Posts mit dem Label MacOSX werden angezeigt. Alle Posts anzeigen
Posts mit dem Label MacOSX werden angezeigt. Alle Posts anzeigen

Samstag, 9. Juli 2011

DTrace and it's impact on JVM performance

So, I did this blogpost that gave a very shallow introduction to what DTrace can do with the JVM and I got an amazing feedback after that. Actually I'm quite flattered that it had such an impact.

One message I received pointed out that DTrace probes are having a significant impact on the JVM's performance, he (the author) would never recommend using the DTrace JVM integration, and then he pointed me… well, a product by his own company (which shall remain nameless, since I don't want to advertise commercial products on this blog). To be fair: I admit that I would do the same. When you put a lot of effort into a piece of software you do it for reasons you believe in.

However, he gave me a link to some benchmarks that showed some impressive results and diagrams about how much the performance of the JVM suffers when DTrace probes are enabled. The bar diagrams were scary, DTrace looked pretty bad compared to the "rival" technology (you can't really call it a rival since DTrace has a completely different objective). But something was fishy about this, and that was: the axes of the diagrams were not labeled. They did show a small blue bar and a big green bar and nothing else. The code for the test case was provided as a gif (no copy and paste to reproduce the results nice and easy). Numbers were not put into any perspective. And blog comments were disabled.

None the less, this was interesting enough to start a little bit of research on the topic.

The benchmarks seemed to focus on how much enabled probes did slow down method calls. I personally don't like benchmarks that use extremely unrealistic cases as a foundation (look how fast I can increment them integers in a tight loop suckaz! Mah language iz teh fast!) but this time I will just go with the flow because this is pretty much what they did in that benchmark (don't try this at home kids, use realistic conditions to benchmark your stuff). I'm not using the same test code here but the idea seems to be pretty much the same.

The system I'm running this on is a Thinkpad X201 with a core i7 and 8 gigs of RAM (yeah I know, I'm just compensating, get over it ;)). The operating system is OpenIndiana Build 151-beta with DTrace 1.7. Java is at 1.6.0_25.

The stupid test case I will be using is this Java program:

class Test {

public int callTest(int i) {
if (i != 0)
callTest(i - 1);

return i;
}

public static void main(String[] args) {
Test t = new Test();

long starttime = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++)
callTest(100)
long endtime = System.currentTimeMillis();

System.out.println(endtime - starttime);
}
}

Once again, this is not a realistic example. I will just use it to demonstrate that there really is an impact.

> java Test
118
> java -XX:+ExtendedDTraceProbes Test
4106

Wow, this really seems to hurt… the programm is about 35 times slower with DTrace probes enabled.

To put this into perspective I will commit a capital crime in programming. I will compare this stupid program in language A to the same stupid program written in language B. Let language B be Python in this case. To be crystal clear about this: this is NOT a comparison of Python and Java performance. I just need some landmarks to make all those numbers meaningful (at least to some extent since this is a very badly chosen scenario to begin with).

Here is our Python test case.

import time

def callTest(i):
if not i == 0:
callTest(i - 1)

return i

r = range(0,1000000)

starttime = time.time()

for i in r:
callTest(100)

endtime = time.time()
print (endtime - starttime)

And the result:

> python test.py
21.9892270565


OK, our software runs slower with probes enabled, but we are still faster than Python and Python's performance is acceptable for a lot of use cases. We now have a slower JVM that can be instrumented. So I'd say: No real harm done.

Now let's use those probes and aggregate some real data. For this test I will use a slightly modified version of j_methodcalls.d, a script by Brendan Gregg that is shipped with the DTrace Toolkit. The script is licensed under CDDL but I did remove the license header here to make it more concise and blog-friendly.

#!/usr/sbin/dtrace -Zs

hotspot$target:::method-entry
{
this->class = (char *)copyin(arg1, arg2 + 1);
this->class[arg2] = '\0';
this->method = (char *)copyin(arg3, arg4 + 1);
this->method[arg4] = '\0';
this->name = strjoin(strjoin(stringof(this->class), "."),
stringof(this->method));
@calls[pid, this->name] = count();
}

Let's run it!

> pfexec ./j_methodcalls.d -c "java -XX:+ExtendedDTraceProbes Test"
[…snip…]
249126

OK, this is A LOT slower, even slower than Python. 4 Minutes!

We are now aggregating data and that means we are copying data from userspace into kernelspace from where it can be fetched by DTrace consumers (in our case the dtrace command line tool). What data did we get in this amount of time? Actually: A LOT. It flooded my terminal and I had to pipe the result into less to be able to read all of it. DTrace recorded every method call that happened in the JVM while the program was running. It counted the calls per method, and the class the method belongs to. Keep in mind that we did not need to modify our code to get these results. We don't even need to restart a running JVM to enable the probes we can activate them by using the jinfo command. And we could have used DTrace to gather system wide data in the same script (something I might demonstrate sometime on this blog)

Now lets use the most naive debugging technique on earth. We will print out "CALLED!" every time our callTest method gets called (if you ever did this before you know how disastrous the result well be). This gives us pretty much no information. We just know that a particular method has been called and we need to modify our code, recompile and load it into running JVM.

> time java Test
[…snip…]
CALLED!
5958514

real 1h39m18.15s
user 3m53.29s
sys 7m55.68s

As we expected, the result is a disaster. Calling print in a tight loop is an extremely stupid thing to do. We could have used a counter that get incremented with every method call, proxy objects, interceptors etc. (all of them would have been significantly faster).

To do something similar like the print example with DTrace I just a another clause to the script:

tick-1s {
printa(@calls);
trunc(@calls);
}

This addition prints out what happened in 1 second intervals

1 75779 :tick-1s
4028 Test.callTest 400379

1 75779 :tick-1s
4028 Test.callTest 404720

1 75779 :tick-1s
4028 Test.callTest 402135

1 75779 :tick-1s
4028 Test.callTest 398934

253064
dtrace: pid 4028 has exited


real 4m14.23s
user 4m13.89s
sys 0m0.46s

The performance impact stays pretty much the same with DTrace, we are done in 4 Minutes while we are presented with a readable stream of information.

There are a lot of ways to generate similar data, but most of them require code changes, are not able to do system wide tracing, are limited to one process and/or just one specific runtime.

Conclusion

Tracing the JVM costs (this shows especially in this pathological use case), but DTrace provides us with a very broad spectrum of probes. The JVM ones are just one source of data. We can actually instrument every part of the system with our DTrace script. Maybe a problem is not even related to our program at all, maybe it's NFS misbehaving, something is wrong with the database or there is some heavy IO going on. With DTrace the whole system becomes transparent. This changes the whole "blame game" and that's the whole point of DTrace. Looking at the system as a whole.

The bottom line is: trace the JVM only if you need to and be aware of the performance impact. This tool is for hunting down problems that are very hard or even impossible to analyze with traditional tools. I did use it to trace obscure memory leaks and dead-locks (both in non-Java contexts) and I was able to exactly pinpoint the culprit.

Don't use DTrace when there is a tool that does a better job for this specific task. Use it wisely. Anyway, it's a great utility to have in your toolbox.

Last but not least: use realistic use cases for benchmarking, label your diagram axes, and compare tools that have the same objective.

Mittwoch, 6. Juli 2011

DTrace and Scala

DTrace is one of my favorite technologies ever, it absolutely redefined my view on software and how it can be debugged. In a lot of situations you are basically forced to debug your software by vomiting print-statements just all over the place. To make things worse you have to take them out when you are actually shipping your software, leaving you blind to errors to come. In other cases you probably are missing some print-statements to trace down a very nasty bug, or the bug is hidden in some 3rd-party lib that would take hours to recompile with your new print statement (don't start thinking about libs that you don't have the code of…). In most cases this can become a very unpleasant situation (to put it mildly). DTrace takes a lot of the hassle away by providing you with a mechanism that can modify your program while it's running without stopping it.
Pretty amazing, huh? Wait, it gets better. You can even trace into the runtime of some high level languages (if they provide the probes). This is also true for the JVM, and that means we can instrument a running Scala program.

In this post I will walk you through a very basic script that shows what methods from Predef get called by your program. DTrace is a tool that reaches deep down into the lower levels of your machine. Sounds scary? Nah not really, one of the design goals of DTrace is safety. Your scripts run in a managed environment that keeps it from doing harmful things.

OK, enough sweet-talk. Time to get our hands dirty by writing a very simple Scala program:

import scala.annotation.tailrec

object Main {
@tailrec
def sayHallo() {
println("HALLO!")
Thread.sleep(1000)
sayHallo()
}

def main(args: Array[String]) {
sayHallo()
}
}

This just prints out "HALLO!" in 1 second intervals (not exactly rocket science, but I put a little sugar on top of it by replacing the while loop with a tail recursive function for fun and profit).

What's that? When running my program DTrace is not showing me ANY probes!!?!?! FFFFUUUUUUUU! That's because we have to enable them first, we can instruct a running JVM to do this by using jinfo. Since I only got one JVM running on this box I will fetch the PID with pgrep.

jinfo -flag +ExtendedDTraceProbes $(pgrep java)

The JVM probes are now armed and "dangerous" (just kiddin') and you will have access to the hotspot provider.

Now lets write the DTrace script. Keep in mind: this script is running in kernel space, so we have to copy in some information from userspace, we do this by using copyin and we have to NULL-terminate the strings ourselves. Yep, this is what it feels like to program low-level stuff, it's not as pretty as FP but aaaaaaaaaanyway, here is the little bugger.

#!/usr/sbin/dtrace -s

#pragma D option quiet

hotspot$$target:::method-entry
{
this->class = (char *) copyin(arg1, arg2 + 1);
this->class[arg2] = '\0';
self->tracefunc = stringof(this->class);
}

hotspot$$target:::method-entry
/self->tracefunc == "scala/Predef$"/
{
this->method = (char *) copyin(arg3, arg4 + 1);
this->method[arg4] = '\0';
printf("%s %Y\n", stringof(this->method), walltimestamp);
self->tracefunc = 0;
}

hotspot$$target:::method-entry
/self->tracefunc/
{
self->tracefunc = 0;
}

This thing will fire whenever a function from Predef is called and will give us the function name (in our test case this is just println) and the time when this function was being called. I run this on OpenIndiana build151-beta by issuing pfexec dtrace ./tracescript.d -p $(pgrep java) after I enabled the hotspot provider on the JVM. (pfexec is kind of like sudo, just use whatever gives you the permission to run dtrace on your box)
The output will look like this:

println 2011 Jul 6 21:27:34
println 2011 Jul 6 21:27:35
println 2011 Jul 6 21:27:36
println 2011 Jul 6 21:27:37
println 2011 Jul 6 21:27:38
println 2011 Jul 6 21:27:39
println 2011 Jul 6 21:27:40
println 2011 Jul 6 21:27:41
println 2011 Jul 6 21:27:42
println 2011 Jul 6 21:27:43
println 2011 Jul 6 21:27:44
println 2011 Jul 6 21:27:45
println 2011 Jul 6 21:27:46
println 2011 Jul 6 21:27:47
println 2011 Jul 6 21:27:48
println 2011 Jul 6 21:27:49
println 2011 Jul 6 21:27:50
println 2011 Jul 6 21:27:51
println 2011 Jul 6 21:27:52
println 2011 Jul 6 21:27:53
println 2011 Jul 6 21:27:54
println 2011 Jul 6 21:27:55


WTF IS THIS I DON'T EVEN!?!?!? TL;DR

OK, this is not even the tip of the iceberg but I think I will wrap it up because there is a lot of ground to cover when it comes to DTrace. If you are hungry for more you should check out the "DTrace Review" by @bcantrill, this stuff will blow your mind (seriously, WATCH IT!) or buy the book by @brendangregg. I will make sure to dig deeper on the topic, so stay tuned. Tell me what you think, good tool or best tool? :P

Samstag, 25. Dezember 2010

Die Sache mit Apple

"Hallo, ich bin raichoo und ich habe ein MacBook". Das sind Sätze mit denen man in vielen Kreisen schnell Freunde finden kann (Vorsicht Ironie!). Aber mal ehrlich, in den letzten Tagen kann man sich besonders schöne Kommentare gefallen lassen. Mein Favorit ist hier "Apfelnazi" (Goodwin's Law lässt grüßen). Die Diskussionskultur lässt hier ziemlich zu wünschen übrig, wer Leute nach dem Betriebssystem beurteilt hat in meinen Augen doch eher einen massiven Schaden.

Aber was bewegt die Leute dazu so zu reagieren? Auf der einen Seite mag das ganze auf einem pseudoelitären Gefühl basieren. Apple ist Mainstream. Gegen Mainstream sein ist rebellisch, cool und was weiß ich noch alles. Ich bin ja selber keine Ausnahme, meine Lebensweise ist sicherlich nicht die eines Ottonormalverbrauchers. Aber diese extremen Anfeindungen gehen doch echt schon zu weit.
Doch zurück zu den Beweggründen. Wikileaks ist im Moment sicherlich ein sehr bewegendes Thema, und das Verhalten vieler Firmen wie Visa, Paypal, Mastercard und auch Apple ist nicht nur verwerflich, es ist in meinen Augen auch undemokratisch.
Was also machen? Boykottieren? Guter Plan, Boykott war immer schon die wirksamste Waffe des Verbrauchers, denkt man sich. Die Problematik die sich hieraus aber ergibt ist folgende: der überwiegende Großteil der Firmen wird so reagieren. Ein bisschen Druck vom Staat eventuell noch ein paar Zuwendungen unter der Hand und schon wird der Stecker gezogen. Unsere Freiheit wird also potentiell von jeder Firma beschnitten die nur den nötigen Einfluss hat, oben genannte Beispiele befinden sich nur gerade in dieser Situation besonders in so einer Position. Man kann sich also praktisch totboykottieren, der Hydra wachsen die Köpfe einfach nach.

Wie also reagieren? Alle Apple Produkte aus dem Fenster werfen in den nächsten Mediamarkt rennen, ein Plastikwaffeleisen kaufen das nach einem Jahr auseinanderfällt und Ubuntu installieren? Ich sag es ehrlich: Linux ist derzeit einfach technisch keine Alternative für mich, und FreeBSD's Treibersituation auf Notebooks ist auch nicht wirklich berauschend. Windows mag ich nicht, usw.. Erreicht wird damit doch praktisch eh nichts. In meinen Augen ist beim Thema Wikileaks das effektivste Mittel doch eh: so hartnäckig spenden wie es nur geht. Wege dafür gibt es genug. Unterstützung ist weitaus mächtiger als Boykott.

Wenn mir also jemand ein Notebook mit 13" Formfaktor, 6 Stunden+ Akkulaufzeit, Alugehäuse, großen Touchpad auf dem mindestens ein FreeBSD rennt (damit meine ich das ALLE Komponenten auch zuverlässige Treiber haben) und das dazu auch noch KEINERLEI Komponenten von irgendeiner Firma enthält die irgendwo mal Scheisse gebaut hat zeigen kann, bring it on. (Das ist ernst gemeint, würde mich interessieren ob es da draußen irgendwas gibt was dem auch nur ansatzweise entspricht)

Dienstag, 30. März 2010

Spekulatives Tracen mit DTrace

Spekulieren ist ja nicht erst seit der "Finanzkrise" der letzte Schrei, auch wir Programmierer stehen hin und wieder mal vor der Situation das wir Kram anhäufen von dem wir nicht wissen ob wir ihn später noch gebrauchen können. Besonders beim Debuggen fallen oft Daten an bei denen wir erst wissen ob sie interessant sind wenn wir schon bis zum Hals in Messwerten stecken. Wie also die Spreu vom Weizen trennen? DTrace hat ein Feature das leider viel zu wenig Beachtung bekommt: Speculations.

Ein einfaches Beispiel: Wir interessieren uns für alles was im Kernel während des Öffnens einer Datei passiert, allerdings wollen wir das Ergebnis nur wissen wenn das Öffnen fehlschlägt. Das macht folgendes D-Script:

#!/usr/sbin/dtrace -s

syscall::open:entry
/execname == "a.out"/ {
self->spec = speculation();
speculate(self->spec);
printf("%s",copyinstr(arg0));
}

fbt:::
/self->spec/ {
speculate(self->spec);
}

syscall::open:return
/self->spec && arg1 == -1/ {
commit(self->spec);
}

syscall::open:return
/self->spec/ {
discard(self->spec);
}


Ok, gehen wir das Ganze mal durch: self->spec = speculation(); legt threadlokal einen Puffer unter dem Namen spec an. Eine Speculation ist also nichts anderes als ein Puffer in dem wir Messwerte zwischenspeichern und darauf warten ob sie wichtig werden.
Um in einer Klausel das "Aufzeichnen" zu starten reicht ein speculate(self->spec), alle darauf folgenden Messwerte wandern in unseren Puffer. Tritt ein Ereignis ein das unseren Werten im Puffer Bedeutung verleiht können wir sie mit commit(self->spec); ausgeben oder im gegenteiligen Fall mit discard(self->spec); verwerfen.

ACHTUNG: Puffer zu füllen ist eine kostspielige Angelegenheit, denkt daran das wir uns hier im Kernelkontext befinden. Also solltet ihr Daten die ihr nicht mehr braucht auch sofort verwerfen. Wachsen eure Puffer ungehindert weiter wird die DTrace VM euch einen Strich durch die Rechnung ziehen und selbst anfangen Daten zu verwerfen damit ihr das System nicht in den Abgrund zieht. Des weiteren ist der Trace den ich oben beschrieben habe auch schon recht "brutal" da der Puffer mit einem kompletten fbt Messdatensatz befüllt wird. Das bremst open-systemcalls übel aus, also: HANDLE WITH CARE!

Dienstag, 16. März 2010

Ganz viel DTrace :D

Heute war ein ziemlich voller Tag. Ich habe unter anderem meinen Vortrag über DTrace den ich schon einmal in der warpzone gegeben habe bei der Mac User Group Gütersloh gehalten. Ich hab versucht dieses doch sehr technische Thema möglichst einfach rüber zu bringen da die Folien eigentlich eher für ein sehr technophiles Publikum gedacht waren. Hoffe ihr habt trotzdem Spass daran ;)


Für alle die was auf die Ohren wollen hatte ich die Ehre eine Sendung mit Mario Heide von Pofacs zu machen. Also: die volle Dröhung DTrace :D

Freitag, 6. November 2009

By popular demand: Meine .kshrc

Ich werde des öfteren mal nach meinen Einstellungen für die ksh93 die ich als meine Defaultshell auf allen Systemen die ich derzeit aktiv verwende einsetze. Der Commandeditor fc hat es mir in dieser Shell besonders angetan ganz zu schweigen davon das sie im Vergleich zu anderen Shells ziemlich bugfrei ist.

Für alle nicht vi-User VORSICHT: Diese Config kommt mit aktiviertem vi-Modus daher ;) Außerdem habe ich PS ein wenig blogtauglicher formatiert.

Noch ein Tipp für die Mac-User: die ksh in der Standardinstallation ist ziemlich veraltet. Schnappt euch Macports und installiert sie euch daraus. Danach die neue Shell in die /etc/shells eintragen und sudo chsh -s /opt/local/bin/ksh username und ksh93 ist eure Defaultshell.


set -o noclobber
set -o ignoreeof
set -o globstar
set -o vi

HISTFILE=$HOME/.histfile.$(tty | cut -d/ -f 3)
RED=$(print -n "\033[0;31m")
GREEN=$(print -n "\033[0;32m")
YELLOW=$(print -n "\033[0;33m")
BLUE=$(print -n "\033[0;34m")
PURPLE=$(print -n "\033[0;35m")
CYAN=$(print -n "\033[0;36m")
WHITE=$(print -n "\033[0;38m")

export HOSTNAME=$(hostname)
export EDITOR="vim"
export HISTEDIT="vim"

_checkdir() {
if [[ $PWD == $HOME ]]
then
print -n "~"
elif [[ $PWD == "/" ]]
then
print $PWD
else
print -n ${PWD##*/}
fi
}

if [[ $(uname) == "Darwin" ]]
then
export PS1='${WHITE}[${GREEN}${USER}${WHITE}@${YELLOW}\
${HOSTNAME}${WHITE}:${BLUE}$(_checkdir)${WHITE}]:\
${RED}$HISTCMD${WHITE}> '
alias ls='ls -FG'
alias pkill='killall'
alias pgrep='ps -A | grep -i'
alias grep='grep --color=always'
elif [[ $(uname) == "SunOS" ]]
then
export PS1='[${WHITE}[${GREEN}${USER}${WHITE}@${CYAN}\
${HOSTNAME}${WHITE}:${BLUE}$(_checkdir)${WHITE}]:\
${RED}${HISTCMD}${WHITE}> '
export PATH="/bin:/sbin:/usr/bin:/usr/sbin:\
/opt/SunStudioExpress/bin:$PATH"
alias ls='ls -F'
elif [[ $(uname) == "FreeBSD" ]]
then
export PS1='[${WHITE}[${GREEN}${USER}${WHITE}@${PURPLE}\
${HOSTNAME}${WHITE}:${BLUE}$(_checkdir)${WHITE}]:\
${RED}${HISTCMD}${WHITE}> '
alias ls='ls -FG'
alias pkill='killall'
alias grep='grep --color=always'
alias pgrep='ps -A | grep -i'
fi

alias !!='fc -s -1'
alias vi='vim'

Sonntag, 6. September 2009

Bug: Snow Leopard's dynamic_cast und explizit instanziierte C++ Templates

Derzeit arbeite ich an einem Projekt welches ich auf dem Mac (allerdings nicht für den Mac) entwickle. Dabei verwende ich unter anderem auch gerne die RTTI (RunTime Type Information) Fähigkeiten von C++ die mir unter anderem auch sichere Downcasts zur Verfügung stellen (Ist ein Downcast nicht möglich wird der Nullpointer zurückgegeben). Das alles klappt durchaus gut, es sei denn ich schreibe eine Lib in der ich bereits die Templates instanziiere die ich später in meiner Applikation nutzen will. Alles kompiliert zwar wunderbar und das Template lässt sich ebenfalls nutzen sobald ich aber auf Polymorphie zurückgreife wird es hässlich, da anscheinend die Typinformationen verloren gehen und dynamic_cast nicht mehr in der Lage ist richtig zu arbeiten.

Der ganze Fehler lässt sich mit folgendem Testfall reproduzieren:


//TestLib.hpp
class Base {
public:
virtual ~Base() {}
};

template <typename T>
class Derived : public Base {
public:
Derived();
};

/*TestLib.cpp
* compile with: g++ -dynamiclib TestLib.cpp -o libTestLib.dylib
*/
#include "TestLib.hpp"
#include <typeinfo>
#include <iostream>

template <typename T>
Derived<T>::Derived() {
std::cout << typeid(*this).name() << std::endl;
}

template class Derived<int>;

Wie man in der letzten Zeile sieht wird das Template Derived explizit mit int in TestLib.cpp instanziiert. Diesen Typen werden wir jetzt in einer kleinen Anwendung etwas durch die Typhierarchie scheuchen und ein sehr seltsames Verhalten beobachten.

/* main.cpp
* compile with: g++ -L. -lTestLib main.cpp -o main
*/
#include "TestLib.hpp"
#include <iostream>

int main() {
Derived<int>* i = new Derived<int>();
std::cout << typeid(*i).name() << std::endl;
Base *o = i;
std::cout << typeid(*o).name() << std::endl;
std::cout << o << std::endl;
std::cout << dynamic_cast< Derived<int>* >(o) << std::endl;
std::cout << typeid(*o).name() << std::endl;
delete i;
}

Wir erhalten folgende Ausgabe:

7DerivedIiE
7DerivedIiE
0x100100080
0
7DerivedIiE

Die Typid verändert sich nicht und trotzdem liefert ein gültiger Downcast den Nullpointer zurück. Ich habe den Code ebenfalls unter FreeBSD und DragonflyBSD getestet, in beiden Fällen war der Downcast erfolgreich. Meines Wissens tritt dieses Verhalten erst seit Snow Leopard auf, aber da ich derzeit nirgends eine aktuelle Version von Leopard laufen habe kann ich nicht testen ob es sich hier um einen neuen Bug handelt.

Der einzige Workaround der mir derzeit einfällt ist die Instanziierung des Templates in die Compilezeit der Anwendung zu verlegen.

UPDATE: Mal ein delete nachgebessert, nicht das sich hier noch jemand beschwert das mein Testcase leakt :P

Montag, 20. Juli 2009

C Funktionen aus C# rufen

Für mich ist es immer wichtig das Sprachen nicht isoliert dastehen. Ich möchte von jeder Sprache oder Laufzeitumgebung ungehindert mit dem System kommunizieren. Da ich mich meistens auf diversen UNIX Systemen herumtreibe ist das größtenteils C. Gerade im Moment hat es mir C# ein wenig angetan und auch hier kann man ohne weiteres mit dem System schnacken. Darum hier mal ein kleines Beispiel das eventuell dem ein oder anderen weiterhilft. Das ganze wurde auf MacOSX mit Mono getestet läuft aber genauso gut unter *BSD und Linux.

Als erstes legen wir eine kleine Library an die die Funktion enthält die wir später aufrufen wollen.

//compile with gcc -dynamiclib test.c -o test.dylib
#include

int test(const char* s) {
printf("Printing from C: %s\n",s);
return 666;
}

Nichts dramatisches, unser Programm soll lediglich etwas Text übernehmen ausgeben und einen Integer an unser C# Programm zurück liefern. Nach dem kompilieren erhalten wir eine dynamische Bibliothek mit Namen test.dylib.

Unser C# Code ist ähnlich spektakulär aufgebaut:

using System;
using System.Runtime.InteropServices;

class PTest {
[DllImport("test.dylib")]
public static extern int test(string s);

public static void Main() {
Console.WriteLine(test("this is a test"));
}
}

Eine interessante Anmerkung nebenbei: test.dylib muss zum Zeitpunkt der Übersetzung nicht existieren.

Jetzt ist man also ohne weiteres in der Lage mit Libs wie z.B. libzfs oder libdtrace von C# aus zu kommunizieren um stark integrierte (aber logischerweise nicht mehr plattformunabhängige) Applikationen zu schreiben.

Dienstag, 16. Juni 2009

LLVM: Neue Compiler braucht das Land!

In letzter Zeit macht die Low Level Virtual Machine immer mehr von sich Reden. Blöderweise ist der Name doch ein wenig verwirrend und man denkt schnell an die Java VM oder dergleichen. In Wirklichkeit handelt es sich um ein Compilerframework (und eine eigene Sprache) welches einem sehr interessante Möglichkeiten eröffnet. Apple verwendet LLVM unter anderem um OpenCL zu realisieren.
Was mich besonders an LLVM interessiert sind vor allem die Möglichkeiten zur Analyse von Fehlern die es bietet. Hier mal gcc und clang (ein C/C++/ObjC Frontend für LLVM) im Vergleich.

$ gcc-4.2 -fsyntax-only t.c
t.c:7: error: invalid operands to binary + (have 'int' and 'struct A')
$ clang -fsyntax-only t.c
t.c:7:39: error: invalid operands to binary expression ('int' and 'struct A')
return y + func(y ? ((SomeA.X + 40) + SomeA) / 42 + SomeA.X : SomeA.X);
~~~~~~~~~~~~~~ ^ ~~~~~

Clang nagelt den Fehler auf Zeile UND Spalte fest, nebenher verbraucht er zum kompilieren auch noch weniger Speicher und ist auch noch schneller. Ziemlich beeindruckend.

LLVM wird sicherlich den Compilermarkt in der Opensource Welt nachhaltig verändern. So gibt es z.B. Ambitionen FreeBSD komplett mit Clang anstelle von GCC zu übersetzen.

Auch die Rubyfraktion will von LLVM profitieren und so experimentiert das MacRuby-Projekt ebenfalls mit LLVM um Ruby auf dem Mac zu beschleunigen.

Für alle die jetzt neugierig geworden sind gibt es hier einen interessanten Tech Talk zum Thema, genauso wie einen Podcast vom Chaosradio Express

Mittwoch, 10. Juni 2009

Vortrag: Inside the Mac OS X Kernel

Diesen Vortrag wollte ich eigentlich schon vor lange Zeit mal verlinken, aber irgendwie habe ich das immer verschwitzt. Sollte sich jeder mal ansehen der sich für Kernel Designs interessiert. Vor allem die Mach IPC und IOKit sind wirklich einen Blick wert.

Samstag, 1. November 2008

ZFS: Eine Einführung

Krank sein nervt, aber wenn man im Bett liegt kann man sich mit ein paar Sachen befassen die man sonst eher selten macht. Ich hab die letzten Tage die ich mit Grippe im Bett gelegen hab einfach mal damit verbracht mich in einige ZFS Features einzuarbeiten und mir Gedanken darüber zu machen wie man sie in einem Blog packt. Damit auch die Linuxwelt etwas davon hat hab ich mich auch gleich noch darum gekümmert ZFS unter Ubuntu zum fliegen zu bringen.

Naja lange Rede kurzer Unsinn.

ZFS! Immer wieder hört man das es sich hierbei "nur" um ein Filesystem handelt. Ich will heute mal zeigen das das zwar stimmt, ZFS aber sehr viel mehr ist. Es ist praktisch ein Storage Werkzeugkoffer der fast alles abdeckt was einem in Sachen Storage so über den Weg laufen kann. Das Element mit dem alles anfängt ist der sogenannte zpool. In einem zpool packen wir alles was wir an Storage so nutzen wollen: Festplatte, USB-Sticks, einfach nur Dateien und was weiß ich noch für Devices. Ich werde hier aufgrund eines notorischen Festplattenmangels einfach ganz normale Files nehmen die jeweils 100MB Größe haben.

ZPools

Als erstes werden wir einfach mal die einfachste Sorte von zpools anlegen, nämlich solche die nur aus einem Datenträger bestehen.

[root@itzkoatl:zfsdemo]> zpool create tank $PWD/disk1
[root@itzkoatl:zfsdemo]> zpool list
NAME SIZE USED AVAIL CAP HEALTH ALTROOT
tank 95.5M 73.5K 95.4M 0% ONLINE -
[root@itzkoatl:zfsdemo]> zpool status tank
pool: tank
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
/export/home/raichoo/zfsdemo/disk1 ONLINE 0 0 0

errors: No known data errors

Das wars eigentlich schon. Alleine durch die Eingabe von zpool create tank $PWD/disk1 haben wir einen neuen zpool erstellt. Kein formatieren, kein mounten. Das File wurde direkt mit einem ZFS Filesystem auf dem mountpunkt /tank angehängt. Das dauert nur wenige Sekunden.
Was aber machen wenn man mehrere Festplatten hat und deren Platz in einem pool verwenden will?

[root@itzkoatl:zfsdemo]> zpool add tank $PWD/disk2
[root@itzkoatl:zfsdemo]> zpool status tank
pool: tank
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
/export/home/raichoo/zfsdemo/disk1 ONLINE 0 0 0
/export/home/raichoo/zfsdemo/disk2 ONLINE 0 0 0

errors: No known data errors
[root@itzkoatl:zfsdemo]> zpool list
NAME SIZE USED AVAIL CAP HEALTH ALTROOT
tank 191M 82.5K 191M 0% ONLINE -

Wir haben die neue Platte einfach durch add in den zpool eingefügt und wir zpool list zeigt hat sich die Kapazität von tank verdoppelt.
Wir können aber auch zpool mit RAID Fähigkeiten erstellen, dazu gibt es die subkommandos mirror, raidz und raidz2.

[root@itzkoatl:zfsdemo]> zpool create tank mirror $PWD/disk1 $PWD/disk2
[root@itzkoatl:zfsdemo]> zpool status tank
pool: tank
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
mirror ONLINE 0 0 0
/export/home/raichoo/zfsdemo/disk1 ONLINE 0 0 0
/export/home/raichoo/zfsdemo/disk2 ONLINE 0 0 0

errors: No known data errors
...
[root@itzkoatl:zfsdemo]> zpool create tank raidz2 $PWD/disk1 $PWD/disk2 $PWD/disk3 $PWD/disk4
[root@itzkoatl:zfsdemo]> zpool status tank
pool: tank
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
raidz2 ONLINE 0 0 0
/export/home/raichoo/zfsdemo/disk1 ONLINE 0 0 0
/export/home/raichoo/zfsdemo/disk2 ONLINE 0 0 0
/export/home/raichoo/zfsdemo/disk3 ONLINE 0 0 0
/export/home/raichoo/zfsdemo/disk4 ONLINE 0 0 0

errors: No known data errors

Man kann noch sehr viel mehr mit zpools anstellen, aber das reicht fürs erste ;).

Filesysteme

Das erste Filesystem haben wir ja schon mit dem Erstellen des zpools angelegt. Es trägt den Namen tank und ist unter /tank gemountet.

[root@itzkoatl:zfsdemo]> zfs list
NAME USED AVAIL REFER MOUNTPOINT
tank 98.6K 158M 26.9K /tank

Wir tun jetzt einfach mal so als würden wir eine Art home-Struktur anlegen wollen. Dazu legen wir ein separates home-Filesystem und ein Filesystem für jeden Benutzer an. ZFS Filesysteme sind in etwa vergleichbar mit dem was man unter herkömmlichen Filesystemen als Partitionen bezeichnet, nur das sie sich in ihrer Größe dem Inhalt anpassen. Ein ZFS Filesystem in dem also nichts liegt wird auch praktisch nichts an Plattenplatz belegen.

[root@itzkoatl:zfsdemo]> zfs create tank/home
[root@itzkoatl:zfsdemo]> zfs create tank/user1
[root@itzkoatl:zfsdemo]> zfs create tank/user2
[root@itzkoatl:zfsdemo]> zfs create tank/user3
[root@itzkoatl:zfsdemo]> zfs list
NAME USED AVAIL REFER MOUNTPOINT
tank 247K 158M 34.4K /tank
tank/home 26.9K 158M 26.9K /tank/home
tank/user1 26.9K 158M 26.9K /tank/user1
tank/user2 26.9K 158M 26.9K /tank/user2
tank/user3 26.9K 158M 26.9K /tank/user3

So wir haben unsere Filesysteme erstellt aber UPS! unsere User sind ja gar nicht an der richtigen Stelle gemountet, eigentlich gehören die ja nach /tank/home. Kein Problem, wir können den Mountpoint im Nachhinein einfach setzen

[root@itzkoatl:tank]> zfs set mountpoint=/tank/home/user1 tank/user1
[root@itzkoatl:tank]> zfs rename tank/user1 tank/home/user1
[root@itzkoatl:tank]> zfs list
NAME USED AVAIL REFER MOUNTPOINT
tank 258K 158M 32.9K /tank
tank/home 55.3K 158M 28.4K /tank/home
tank/home/user1 26.9K 158M 26.9K /tank/home/user1
tank/user2 26.9K 158M 26.9K /tank/user2
tank/user3 26.9K 158M 26.9K /tank/user3

Das rename ist eigentlich nicht nötig, ich habe es allerdings hier aus Schönheitsgründen mal gemacht ;). Durch das Umsetzen des Mountpoints wird das Filesystem von seinem alten Standort ausgehängt und am neuen Mountpoint eingehängt, alles automatisch.
Wir räumen jetzt einfach mal die anderen Filesysteme weg und arbeiten nur noch mit user1 weiter.

[root@itzkoatl:tank]> zfs destroy tank/user2
[root@itzkoatl:tank]> zfs destroy tank/user3


Filesystem Attribute

Jetzt zu ein paar interessanten Attributen die man mit set und get setzen und auslesen kann. Ich werde nur ein paar davon zeigen weil es wirklich eine ganze Menge sind, aber ich halte diese für die praktischsten.

Reservation

Hiermit läßt sich Plattenplatz aus dem zpool reservieren. Dem Filesystem wird also eine bestimmte Menge Storage zugesichert, wie man unten sieht hat tank/home/user1 10MB mehr Speicher zur Verfügung als alle anderen Filesysteme.

[root@itzkoatl:tank]> zfs set reservation=10m tank/home/user1
[root@itzkoatl:tank]> zfs list
NAME USED AVAIL REFER MOUNTPOINT
tank 10.2M 148M 28.4K /tank
tank/home 10.0M 148M 28.4K /tank/home
tank/home/user1 26.9K 158M 26.9K /tank/home/user1


Quotas

Was man reservieren kann, kann man aus begrenzen. Mit Quotas lassen sich Filesysteme klein halten.


[root@itzkoatl:tank]> zfs set quota=10m tank/home/user1
[root@itzkoatl:tank]> zfs list
NAME USED AVAIL REFER MOUNTPOINT
tank 10.2M 148M 28.4K /tank
tank/home 10.0M 148M 28.4K /tank/home
tank/home/user1 26.9K 9.97M 26.9K /tank/home/user1


Compression

Der Name sagt es schon. Filesysteme lassen sich transparent komprimieren. Es gibt unterschiedliche Algorithmen wir nehmen hier gzip als Beispiel.

[root@itzkoatl:tank]> zfs set compression=gzip tank/home/user1
[root@itzkoatl:tank]> zfs get compression tank/home/user1
NAME PROPERTY VALUE SOURCE
tank/home/user1 compression gzip local

Hier sollte man daran denken das nur Dateien komprimiert werden die nachträglich im Filesystem erstellt werden. Auch zeigt ls nicht die komprimierte sondern die reale Größe der Datei an.

NFS

Besonders praktisch ist NFS Sharing. Ich werde hier nur die einfachste Form zeigen, aber anstatt on lassen sich die normalen NFS Optionen für das Filesystem angeben. Unter Solaris wird hier alles automatisch eingerichtet und gestartet so das das eingeben einer einzige Zeile reicht um Filesysteme zu sharen (keine Ahnung wie das unter anderem Systemen ist).

[root@itzkoatl:tank]> zfs set sharenfs=on tank/home/user1


Das sind nur ein paar der vielen Attribute die ZFS bietet, alle zu zeigen würden den Rahmen sprengen ;)

Snapshots

Kommen wir zu einem meiner Lieblingsfeatures: Snapshots. Mit Snapshots lassen sich Filesysteme zu einem bestimmten Zeitpunkt einfrieren und auch wieder zurückspielen (und das innerhalb von ein paar Sekunden und ohne das es extra Plattenplatz belegt). Es ist ebenfalls jederzeit möglich in angelegt Snapshots reinzugucken. Wir werden jetzt einfach mal folgendes machen: Wie legen eine Datei mit dem Text "Das ist ein Test" an, danach erstellen wir einen Snapshot und werden die Datei verändern.

[root@itzkoatl:user1]> echo "Das ist ein Test" > text
[root@itzkoatl:user1]> ls
text
[root@itzkoatl:user1]> cat text
Das ist ein Test
[root@itzkoatl:user1]> zfs snapshot tank/home/user1@kleinertest

Ok wir haben den Zustand unseres Filesystems jetzt unter dem Snapshot mit dem Namen kleinertest gesichert. Nun wollen wir unsere Datei mal kaputtmachen und alles wieder herstellen.
[root@itzkoatl:user1]> echo "Ich mach alles kaputt!" >| text 
[root@itzkoatl:user1]> cat text
Ich mach alles kaputt!
[root@itzkoatl:user1]> cat .zfs/snapshot/kleinertest/text
Das ist ein Test
[root@itzkoatl:user1]> zfs rollback tank/home/user1@kleinertest
[root@itzkoatl:user1]> cat text
Das ist ein Test

Wie man sieht existiert ein versteckter Ordner .zfs, dieser wird nicht von ls -a angezeigt (Es sei denn man setzt ein bestimmtes Attribut) sondern praktisch on-the-fly erstellt wenn man explizit auf ihn zugreift. Mit zfs rollback spulen wir das Filesystem wieder zu dem Zeitpunkt zurück an dem wir den Snapshot kleinertest erstellt haben (der Snapshot selber existiert weiter). Snapshot sind nicht beschreibbar aber es lassen sich mit zfs clone schreibbare Filesysteme aus einem Snapshot erstellen.

Serialisieren

Ein Filesystem läßt sich in eine einzelne Datei ausgeben die sich dann verschicken läßt und woanders wieder in ein Filesystem umwandeln läßt (sehr praktisch für Backups). Dazu brauchen wir erst einmal einen Snapshot, dieser läßt sich mit zfs send serialisieren und mit zfs receive wieder "entpacken".

[root@itzkoatl:user1]> zfs send tank/home/user1@kleinertest > /tank/dump
[root@itzkoatl:user1]> ls -l /tank/dump
-rw-r--r-- 1 root root 15680 Nov 1 17:29 /tank/dump
[root@itzkoatl:user1]> zfs receive tank/home/user2 < /tank/dump
[root@itzkoatl:user1]> cat /tank/home/user2/text
Das ist ein Test
[root@itzkoatl:user1]> zfs list
NAME USED AVAIL REFER MOUNTPOINT
tank 10.2M 148M 45.6K /tank
tank/home 10.1M 148M 31.4K /tank/home
tank/home/user1 29.1K 9.97M 29.1K /tank/home/user1
tank/home/user2 27.6K 148M 27.6K /tank/home/user2

Wir haben also nun aus der dump-datei einfach ein neues Userverzeichnis erstellt welches den Zeitpunkt wiederspiegelt an dem wir kleinertest erstellt haben. Snapshots lassen sich auch separat mit zfs list -t snapshot anzeigen

[root@itzkoatl:user1]> zfs list -t snapshot
NAME USED AVAIL REFER MOUNTPOINT
tank/home/user1@kleinertest 0 - 29.1K -
tank/home/user2@kleinertest 0 - 27.6K -

Wie man sieht waren in unserem dump sogar alle Snapshots des Filesystems erhalten (user2 hat ebenfalls einen Snapshot kleinertest)

Import und Export

Filesysteme müssen manchmal mobil sein, z.b. wenn sie auf USB-Sticks liegen. Hierzu kann man zpools einfach exportieren. Exportierte zpools sind ohne das man sie wieder importiert nicht benutzbar (sie werden auch automatisch ausgehängt etc). Steckt man z.b. einen USB-Stick mit einem zpool in das System ein, reicht unter Solaris ein zpool import und alle exportierten Ports werden angezeigt. Ohne Paramter durchsucht dieser Befehl automatisch alle Datenträger nach exportierten zpools im /dev Filesystem.
Da unsere disk Dateien jetzt aber keine echten Devices sind müssen wir den Ort an dem zpool import suchen soll explizit angeben.

[root@itzkoatl:~]> zpool export tank
[root@itzkoatl:~]> zpool import -d ./zfsdemo
pool: tank
id: 14691414290482700440
state: ONLINE
action: The pool can be imported using its name or numeric identifier.
config:

tank ONLINE
raidz2 ONLINE
/export/home/raichoo/zfsdemo/disk1 ONLINE
/export/home/raichoo/zfsdemo/disk2 ONLINE
/export/home/raichoo/zfsdemo/disk3 ONLINE
/export/home/raichoo/zfsdemo/disk4 ONLINE
[root@itzkoatl:~]> zpool import -d ./zfsdemo tank


Das war ein kleiner aber recht umfangreicher Ausflug in die Welt von ZFS. Und vielen dürfte jetzt klar sein das ZFS mehr ist als nur Volumemanager und Filesystem in einem, es ist ein Storage Verwaltungstools. ZFS ist inzwischen neben Solaris auf FreeBSD, MacOSX und Linux (nur über FUSE) verfügbar. An weiteren Ports wird gearbeitet.

Montag, 13. Oktober 2008

OpenSolaris 2008.11 ermöglicht Zeitreisen

Jeder der sich schon mal einen Mac angesehen hat kennt vermutlich Time Machine: das mitgelieferte Backup Tool von Apple. Ein ziemlich praktisches Feature, das in ähnlicher Form für den nächsten Release von OpenSolaris im November angekündigt ist. Das Ganze wird mit Hilfe von ZFS realisiert (schon mal ein großer Vorteil gegenüber Mac OS X welches IIRC rsync nutzt) und in Nautilus eingebettet, ein SMF Service sorgt im Hintergrund für regelmäßige Snapshots die sich dann per "Time Slider" durchforsten lassen.
Ob es irgendwann auch ein funky Compiz Plugin geben wird das wie auf dem Mac einen Flug durch einen Zeittunnel realisiert bleibt wohl abzuwarten ^^.

Mehr Informationen finden sich hier.

Update: Mir wurde gerade gesagt das Mac OS X doch kein rysnc verwendet sondern "die FSEvent API" (was immer das ist ^^). Danke Okona ;)

Sonntag, 3. August 2008

Dtrace überall

In den letzten Jahren hat dtrace, das dynamische Tracingtool von OpenSolaris, für eine Menge aufsehen gesorgt und inzwischen findet es auf immer mehr Plattformen ein zuhause. Ports existieren bereits für Mac OS X und QNX. Derzeit werden auch vom FreeBSD (man munkelt auch über einen NetBSD Port) Team Anstrengungen unternommen es zu portieren und vor einigen Monaten wurde der erste Code in den CURRENT Zweig commited.
Unter Linux gab es allerdings Probleme da die CDDL-Lizenz, unter der dtrace steht, nicht kompatibel zur GPL ist. Es wurden zwar mit systemtap Ansätze unternommen dtrace zu kopieren, doch ist es in keiner Weise vergleichbar da es unter anderem nicht in der Lage ist Userspace Prozesse zu tracen und die Stabilität des Systems beeinträchtigt da unter anderem das "Skripten" welches fast C ähnlich ist, völlig über das Ziel hinausschiesst und gefährlichen Code ermöglicht. Inzwischen ist das ftrace-Framework im Gespräch welches aber im Gegensatz zu dtrace und systemtap nicht skriptbar ist und damit extrem an Flexibilität einbüßt. Alles in allem eher spärliche Aussichten. Für ein Licht am Ende des Tunnels sorgt Paul Fox der sich daran gemacht hat dtrace auf Linux zu portieren. Ob es letztendlich möglich sein wird Linux Distributionen mit dtrace auszuliefern kann ich derzeit leider nicht sagen (da mir der ganze Lizenzkram langsam eh zu undurchschaubar wird), aber es bleibt die Hoffnung das es wenigstens ein Patchset geben wird das dieses unglaublich mächtige Tool in die Linuxwelt bringen wird.