(WS09-02) Performance-Instrumentierung von OSGi-basierten Anwendungen

Aus Verteilte Systeme - Wiki
Zur Navigation springen Zur Suche springen

Aufgabe

Grundlagen

OSGI

OSGi wird von der OSGi Alliance als dynamische Modulsystem für Java definiert. Die 3 entscheidenen Mermale von OSGi sind Dynamik, Modularisierung und das serviceorientiere Programmiermodell.

Modularisierung:

Ist ein Mittel um komplexe Probleme besser zu lösen. Die Probleme werden in kleine Probleme aufgeteilt. Java bietet zwar schon Möglichkeiten der Modularisierung jedoch fehlt ein Konzept "oberhalb" von Packages. Mithilfe von OSGi lässt sich diese Lücke schließen und anstelle von monolithischen Systemen entstehen Systeme die ais verschiedenen Modulen mit öffentlichen Schnittstellen bestehen.


Dynamik

Mithilfe von OSGi ist es möglich das Laufzeitverhalten genauere zu spezifieren. Es wird festgelegt wie die einzelnen Module installiert, aktualisiert und entfernt werden können. Es ist möglich, sofern keinen großen Abhängigkeiten zwischen den Modulen bestehen, Module dynamisch zu starten und stoppen ohne das andere Module davon betroffen werden.


Serviceorientiertes Programmiermodell

Module können Objekte als Services bereitstellen, indem diese an der OSGi Registry registriert werden. Die Services können von anderen Modulen abrufen und dadurch untereinander Kommunizieren. Das führt zu einer losten Kopplung der beteiligten Modulen

Seeberger.png

Diese 3 einzelnen Mermale an sich sind keine neuen Ideen aber die Verbindung führt zu folgenden Nutzpotenzialen

   * Erhöhte Flexibilität durch Trennung von API und Implementierung
   * Wiederverwendung von Modulen
   * standardisiertes Lifecycle Management, z.B. Hot Deployment oder parallelen Betrieb mehrerer Modulversionen
   * gute Testbarkeit aufgrund von loser Kopplung
   * ....

 

Seeberger diag.png

Bundles

Bundles sind Java Archive (.jar) und enthalten neben den Klassen und Ressourcen noch eine Manifest Datei. Diese Datei beschreibt die Eigenschaften eines Bundles. Hier werden die Abhängigkeiten und die öffentlichen Schnittstellen (API) deklariert. Ein Bundle kann nur auf das API eines anderen Bundles zugreifen, wenn es entsprechende Abhängikeiten deklariert. Diese stellt das OSGi Framework sicher.

Module Layer

Die strukturellen Aspekte rund um Bundles werden im Module Layer definiert.


Lifecycle Layer

Der Lifecycle Layer bringt die dynamische Sicht in das Modell. Der Lifecycle Layer übernimmt die definition des Lebenszyklus. Damit Bundles zur Laufzeit installiert und deinstalliert werden können.


Service Layer

Das serviceorientierte Programmiermodell wird durch den Service Layer hinzugefügt. Dadurch können Bundles in bestimmten Phasen ihres Lebenszyklus Objekte als Services an der OSGi Service Registry anmelden oder abrufen. Das Service-Interface, das vom Service implementiert wird, als Schlüssel verwendet. Ein Augenmerk muss auf die Laufzeit dynamik gelegt werden. Die Bundles die Services anbieten können jederzeit kommen und gehen. Damit müssen die Servicekonsumenten umgehen können. OSGi bietet Mechanismen an, um den Service Lifecycle zu verfolgen.

Security Layer

Diese Schicht stellt Security spezifische Sicherheitsbelange zur Verfügung. Es ist möglich Bundles zu signieren Außerdem spielen darin für OSGi spezifische Permissions eine zentrale Rolle.

Execution Environments

Unterhalb der Framework-Schichten definiert OSGi in Form von Execution Environments Abstraktionen von Java-Laufzeitumgebungen. Es wird keine feste Laufzeitumgebung angegeben sondern nur beschrieben welche Klassen und Methoden verfügbar sein müssen.

Aspektorientierte Programmierung

Aspektorientierte Programmierung (AOP) ist ein Ansatz Komponenten nicht nur logisch sondern auch physisch voneinander zu trennen. So hat die Instrumentierung einer Anwendung mit der eigentliche Anwendung nicht zu tun. Um direkt bei OSGi zu bleiben so findet die Instrumentierung nicht in den einzelnen Bundles statt. Es wird ein extra Bundle erstellt und deployed das sich um die Instrumentierung kümmert. Aspectj ist eine AOP Programmiersprache die auf Java aufbaut. In diesem Projekt verwende ich Equinox Aspects. Es ist ein OSGi Portierung von Aspectj. Das Ziel von AOP ist, das es möglich ist, sich in den laufenden Programmfluß einzumischen. Dabei soll es nicht nötig sein den Quellcode der Anwendung zu verändern. Dabei "webt" sich der Aspekt in den Binärcode ein.


Es gibt in der AOP einige Begriffe die im folgenden kurz erklärt werden.

Aspekt:

Ein Aspekt ist ein Java Klasse mit Einschränkungen. Es können z.B keine direkte Instanzen eines Aspektes erstellt werden. Ein Aspekt besteht aus einer Menge von Advices und Pointcuts

JoinPoint:

Ein Join-Point ist ein wohldefinierter Punkt innerhalb der Programmausführung. Ein JoinPunkt ist eigentlich eine Stelle im Programmcode an den sich der Aspekt einweben kann.

Pointcut:

Ein Pointcut ist eine Menge von Join-Points. Es beinhaltet die JoinPoint in die sich ein bestimmter Advice eingewebt werden soll. Die Wahl der richtigen JoinPoint kann eine schwierige Aufgabe. Es gibt viele Möglichkeiten eine Pointcut "zusammenzubauen".

Möglichkeiten für JoinPoints:

call()
Wählt Join-Point bei Methoden-/Konstruktor Aufruf aus.
execution()
Wählt alle Join-Points der Typ-Hierarchie beim Aufruf der Methode/Konstruktur aus.
within()
Wählt alle Join-Points aus, wo definierter Code dieser und seiner Sub-Klassen steht.
get()
Wählt Join-Point wo Nicht-Statisches Feld-Attribut referenziert (aufgerufen)wird.
set()
Wählt Join-Point wo Nicht-Statisches Feld-Attribute einen Wert zugewiesen bekommt.
preinitialization
Wählt Join-Point vor der Initialisierung einer Variable
initialization()
Wählt Join-Point bei der Initialisierung einer Variable.
handler()
Wählt Join-Point einer gewählten Exception
...

Advice

Advices sind Anweisungen die dem Pointcut folgen. Dabei besitzen die Advices Kenntnis über statische - und Laufzeitinformationen der getroffenen Join-Points. Advices bestehen aus normalen Java - und Aspektcode Ein Advice kann am Join-Point an 3 unterschiedlichen Stellen ausgeführt werden:

  • before(): vor dem Aufruf
  • around(): Fängt die Ausführung ab.
  • after:
    • after(): returning: nach dem return statement
    • after(): throwing: nachdem eine Exception geworfen wurde
    • after(): returning oder throwing

Spezielle Advice Formen:

proceed()
nur in around verfügbar. Mithilfe von proceed() lässt sich die "normale" Ausführung fortführen.
thisJoinPoint
Informationen über diesen Join-Point.
thisJoinPointStaticPart
Äquivalent zu thisJoinPoint.getStaticPart() aber Resourcenschonender
thisEnclosingJoinPointStaticPart
Statische Informationen des umschliessenden Join-Points.

Problemanalyse

Ziel dieses Projektes eine Instrumentierung von OSGi Anwendung zu erstellen. Dabei ist es das Ziel die Instrumentierung möglichst generisch zu halten. Es soll dabei nicht notwendig sein bestehende Anwendungen zu verändern.

Außerdem soll es Konfigurierbar sein. Die Instrumentierung soll zu Laufzeit aktivierbar sein. Dies ist notwendig damit die Anwendungen keine Performance Einbußen haben, wenn kein Bedarf besteht. Außerdem sollte eine Möglichkeit geschaffen werden die Informationen zu sammeln.

Als OSGi Framework soll vorzugsweise Apache Felix eingesetzt werden. Alternativ ist auch Equinox verwendbar.


Ansätze


Ein möglicher Ansatz ist, das sich jede Anwendung um die Instrumentierung kümmert. Das würde jedoch dazu führen, dass es eine starke Verknüpfung zwischen der Instrumentierung und der Anwendung besteht. Dieser Ansatz würde die Komplexität der Entwicklung einzelner Anwendung erhöhen.

Laut der Aufgabenstellung soll überprüft werden ob neue Features der OSGi Spezifikation 4.2 verwendet werden können. Es gibt das Konzept der Service Hooks. Zwar sind auch einige Ideen dabei die für die Instrumentierung verwendet werden kann, wie z.B. der Event Hook, aber leider werden noch nicht alle Features von 4.2 in den aktuellen Implementierungen unterstützt. Wie einem Event Hook ist es möglich einen Proxy vor den Service Aufruf zu setzen. Der Service Hook erkennt wenn ein Service aufgerufen wird.

Ein weitere Ansatz ist, das Listener Konzept der OSGi Spezifikation zu verwenden. Da würde den Vorteil bringen das keine zusätzliche Kompenten verwendet werden. Außerdem wäre es sehr generisch, da das Framework sich über die Sammlung der Informationen kümmert. Leider bieten die Listener nur bestimmte Informationen an. Dieser Ansatz schränkt die Anzahl der Instrumentierungsmöglichkeiten stark ein.

Ein weitere Ansatz ist der Einsatz von Aspektorientierter Programmierung. Durch diesen Ansatz ist eine "sehr" generische Lösung möglich. Die bestehenden Anwendung müssen nicht wissen das sie Instrumentiert werden. Es ist nur notwendig, das es eine OSGi Anwendung gibt, die mittels Aspektorientierter Programmierung, sich zwischenen die Funktionsaufrufe schaltet. Damit die es möglich ist, die Instrumentierung ein und auszuschalten, wird JMX verwendet.

Konzept

Damit eine gewisse Abstraktion möglich ist, wird die Instrumentierung mithilfe von Aspektorientierter Programmierung realisiert. Dazu wird das OSGi Framework Equinox und die dazu passende Aspectj Portierung Equinox Aspects verwendet. Die Realisierung der Instrumentierung findet im einem eigenen Bundle statt. Dadurch entsteht eine zentrale Komponente zur Instrumentierung von OSGi-basierten Anwendungen.

Die Konfiguration der einzelnen Aspekte findet über eine XML Datei statt. In dieser wird definiert auf welche Anwendungen instrumentiert werden sollen. Damit dieser Mechanismus im laufendem Betrieb ein und wieder ausgeschaltet werden kann, wird mithilfe von JMX eine Möglichkeit eingebaut die Aspekte über einen Schalter zu Aktivieren oder zu Deaktivieren.

Das ist Überblick wie das Aspect-Bundle aufgebaut ist.

De.mngtSys.instr.simpleAspectsClassDiagram.png

Implementierung

Das sind die Service Interface die in der Beispiel Implementierung instrumentiert werden sollen. Es gibt verschiedene Varianten der Implentierung. Bei allen Varianten bleiben die Services gleich. Es sind nur verschiedenen Konfigurationmöglichkeiten.


SimpleService

package de.mngtSys.instr.demo.service;

import java.math.BigInteger;

public interface SimpleService {
	String sayHello(String message);
	String sayGoodBye(String message);
	BigInteger fac(int number);
}

SimpleSecondService

package de.mngtSys.instr.demo.secondService;

public interface SecondService {
	String saySomethingDifferent();
}


Manifest

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: SimplesAspect2
Bundle-SymbolicName: projekt.SimplesAspect2
Bundle-Version: 1.0.0
Bundle-Activator: de.mngtSys.instr.simpleAspects.Activator
Import-Package: de.mngtSys.instr.demo.secondService,
 de.mngtSys.instr.demo.service,
 org.apache.log4j;version="1.2.13",
 org.osgi.framework;version="1.3.0"
Eclipse-SupplementBundle: projekt.SimpleSecondService,projekt.SimpleService
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: com.springsource.org.aspectj.runtime;bundle-version="1.6.6";visibility:=reexport
Export-Package: de.mngtSys.instr.simpleAspects;aspect-policy:=opt-out;aspects="AspectInstr"

AbstractAspectInstr

Das ist der Abstrakte Aspekt, von die Aspekte erben, die in der XML Datei definiert werden.

Variante 1

package de.mngtSys.instr.simpleAspects;

import org.apache.log4j.Logger;

public aspect AspectInstr {

	public final Logger logger = Logger.getLogger(this.getClass());

	// Alle Methoden
	pointcut methodExecution():(execution(* **(..)));

	pointcut timeCheck():(within(de.mngtSys.instr.demo.service..*) || within(de.mngtSys.instr.demo.secondService..*));

	pointcut joinPointInfo():(within(de.mngtSys.instr.demo.service..*) || within(de.mngtSys.instr.demo.secondService..*));

	// Before Advice um JoinPoint informationen auszugeben
	before():joinPointInfo() && methodExecution(){
		if (!TriggerFields.getInstance().getTrigger(TriggerFields.JP_INFO_ID)) {

			logger.debug("JP_INFO is off");
			return;

		}
		StringBuilder info = new StringBuilder();
		info.append("JP :" + thisJoinPoint);
		info.append("\n");
		info.append("JP_STATIC: " + thisJoinPointStaticPart);
		info.append("\n");
		info.append("Enc: " + thisEnclosingJoinPointStaticPart);
		info.append("\n");
		logger.debug(info.toString());
	}

	// Advice zur Zeitmessung
	Object around() :  timeCheck() && methodExecution(){
		if (!TriggerFields.getInstance().getTrigger(
				TriggerFields.TIME_TRIGGER_ID)) {

			logger.debug("TimeCheck is off");
			return proceed();
		}

		long stime = System.currentTimeMillis();
		Object o = proceed();
		long etime = System.currentTimeMillis();

		StringBuilder info = new StringBuilder();

		info.append(thisJoinPoint);
		info.append(" dauerte " + (etime - stime) + "ms");

		logger.debug(info.toString());
		logger.debug("Rückgabewert : " + o.toString());
		return o;
	}

}

Variante 2 & 3

 
package de.mngtSys.instr.simpleAspects;

import org.apache.log4j.Logger;

public abstract aspect AbstractAspectInstr {

	public final Logger logger = Logger.getLogger(this.getClass());

	// Alle Methoden
	pointcut methodExecution():(execution(* **(..)));

	// Abstrakte Aspekte, werden in aop.xml definiert
	abstract pointcut timeCheck();

	abstract pointcut joinPointInfo();

	// Before Advice um JoinPoint informationen auszugeben
	before():joinPointInfo() && methodExecution(){
		if (!TriggerFields.getInstance().getTrigger(TriggerFields.JP_INFO_ID)) {

			logger.debug("JP_INFO is off");
			return;

		}
		StringBuilder info = new StringBuilder();
		info.append("JP :" + thisJoinPoint);
		info.append("\n");
		info.append("JP_STATIC: " + thisJoinPointStaticPart);
		info.append("\n");
		info.append("Enc: " + thisEnclosingJoinPointStaticPart);
		info.append("\n");
		logger.debug(info.toString());
	}

	// Advice zur Zeitmessung
	Object around() :  timeCheck() && methodExecution(){
		if (!TriggerFields.getInstance().getTrigger(
				TriggerFields.TIME_TRIGGER_ID)) {

			logger.debug("TimeCheck is off");
			return proceed();
		}

		long stime = System.currentTimeMillis();
		Object o = proceed();
		long etime = System.currentTimeMillis();

		StringBuilder info = new StringBuilder();

		info.append(thisJoinPoint);
		info.append(" dauerte " + (etime - stime) + "ms");

		logger.debug(info.toString());
		logger.debug("Rückgabewert : " + o.toString());
		return o;
	}

}


aop.xml

In dieser Datei werden die konkreten Aspekte definiert. In diesem Beispiel werden die Aspekte FirstAspect und SecondAspect definiert. Sie erben beide von AbstractAspectInstr und unterscheiden sich nur in den Pointcut definitionen. Jeder dieser Aspekte Instrumentiert nur einen Service.

Variante 1

Siehe Manifest Datei, wie die einzelnen Aspecte definiert werden.


Variante 2

<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
	<aspects>
		<concrete-aspect name="de.mngtSys.instr.simpleAspects.FirstAspect"
			extends="de.mngtSys.instr.simpleAspects.AbstractAspectInstr">
			<pointcut name="timeCheck" expression="within(de.mngtSys.instr.demo.service..*)" />
			<pointcut name="joinPointInfo" expression="within(de.mngtSys.instr.demo.service..*)" />
		</concrete-aspect>
		<concrete-aspect name="de.mngtSys.instr.simpleAspects.SecondAspect"
			extends="de.mngtSys.instr.simpleAspects.AbstractAspectInstr">
			<pointcut name="timeCheck"
				expression="within(de.mngtSys.instr.demo.secondService..*)" />
			<pointcut name="joinPointInfo"
				expression="within(de.mngtSys.instr.demo.secondService..*)" />
		</concrete-aspect>

		-->
	</aspects>
	<weaver options="-verbose -debug -showWeaveInfo" />

</aspectj>

Variante 3

Die einzelnen aop.xml Dateien die die Service im META-INF Verzeichnis haben müssen

<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
	<aspects>
		<concrete-aspect name="de.mngtSys.instr.simpleAspects.FirstAspect"
			extends="de.mngtSys.instr.simpleAspects.AbstractAspectInstr">
			<pointcut name="timeCheck" expression="within(de.mngtSys.instr.demo.service..*)" />
			<pointcut name="joinPointInfo" expression="within(de.mngtSys.instr.demo.service..*)" />
		</concrete-aspect>
		


	</aspects>
	<weaver options="-verbose -debug -showWeaveInfo" />

</aspectj>


<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
	<aspects>
		
		<concrete-aspect name="de.mngtSys.instr.simpleAspects.SecondAspect"
			extends="de.mngtSys.instr.simpleAspects.AbstractAspectInstr">
			<pointcut name="timeCheck"
				expression="within(de.mngtSys.instr.demo.secondService..*)" />
			<pointcut name="joinPointInfo"
				expression="within(de.mngtSys.instr.demo.secondService..*)" />
		</concrete-aspect>

	
	</aspects>
	<weaver options="-verbose -debug -showWeaveInfo" />

</aspectj>

JMX MBean

Die eigentlich JMX MBean. Diese Operation stehen zum an und ausschalten der Aspekte zur Verfügung.

package de.mngtSys.instr.simpleAspects;

import java.util.HashMap;

public interface TriggerFieldsMBean {
	public void switchTrigger(int triggerID);
	public boolean getTrigger(int triggerID);
	public HashMap<String, Integer> getAllTriggerID();
	
}
public class Activator implements BundleActivator {

	private Logger logger = Logger.getLogger(this.getClass());

	private MBeanServer mbs;
	private ObjectName name;

	
	public void start(BundleContext arg0) throws Exception {
		mbs = ManagementFactory.getPlatformMBeanServer();
		name = new ObjectName("de.mngtSys.instr:type=Aspect");
		TriggerFieldsMBean mbean = TriggerFields.getInstance();
		mbs.registerMBean(mbean, name);
		logger.debug("Bean Registered");

	}

	
	public void stop(BundleContext arg0) throws Exception {
		mbs.unregisterMBean(name);
		logger.debug("Bean unRegistered");

	}

}

Fazit

Aspektorientierte Programmierung bietet dem Entwickler viel Vorteile bei der Intrumentierung von Anwendungen. Leider existiert aktuell nur Equinox Aspects als geignetet Portierung für eine OSGi Framework. Bei der Entwicklung gab es viele Fallstricke, die im Laufe der Zeit gelöst werden konnten. Leider konnte bis jetzt nicht alle Probleme gelöst werden.

Bei dem Versuch ein einfaches Beispiel Standalone, also außerhalb von Eclipse, zum Laufen zu bekommen, gab es zwar kleinere Probleme aber es funktionierte. Jedoch bei dem Versuch es etwas komplexer zu gestalten gabe es Probleme.

Es wäre vielleicht besser bei der Entwickler auf Spring DM umzusteigen. Wenn von allen Frameworks die 4.2 Spezifikation voll unterstützt wird, wäre auch der Einsatz von Service Hooks interessant.

Quellen

Aspektorientierte Programmierung [1]

Equinox Aspects [2]

Equinox[3]

Neues in Equinox Aspects [4]