Linux Tracing Infrastruktur: Tiefere Funktionen und gängige Anwendungsszenarien
Anbieter zum Thema
In den ersten beiden Teilen dieser Artikelserie haben wir uns mit den Grundlagen der Linux Tracing Infrastruktur und verschiedenen Werkzeugen zu deren Nutzung beschäftigt. Wir möchten uns in der Fortsetzung nun mit einigen tiefergreifenden Funktionen und gängigen Anwendungsszenarien auseinandersetzen. Dies umfasst die Nachverfolgung von Funktionsaufrufen und Abläufen im Betriebssystem, sowie das Generieren von Events aus einer Applikation heraus.

Tracer
Die Analyse von bestimmten Fehlerszenarien erfordert meist dasselbe oder zumindest ein ähnliches Vorgehen und häufig auch eine bestimmte Strategie. Auch hierfür stellt uns die Linux Tracing Infrastruktur einige sehr mächtige Werkzeuge zur Seite: Die sogenannten Tracer.
Neben der reinen Aufzeichnung von Events bieten die Tracer zusätzlich bereits eine bestimmte Heuristik oder sind einem bestimmten Zweck gewidmet. Dies kann zum Beispiel die Untersuchung von Interrupt- oder Schedulinglatenzen sein, oder das Nachverfolgen von Funktionsaufrufen. Im Gegensatz zum reinen Eventtracing kann das Übersetzen des Linux Kerns mit bestimmten Tracer Funktionalitäten bereits einen Einfluss auf das zeitliche Verhalten des Systems haben. Deswegen sind auf einem Produktivsystem üblicherweise nicht alle, sondern nur bestimmte Tracer verfügbar.
Welche Funktionen genau zur Verfügung stehen, lässt sich ganz einfach mit folgendem Kommando herausfinden:
$ cat available_tracers
function_graph function nop
In diesem Beispiel stehen der function und der function_graph Tracer zur Verfügung; auf beide werden wir im Folgenden näher eingehen. Beginnen wir zunächst mit dem sogenannten function Tracer: Dieser ist dazu gedacht, Codepfade zu untersuchen und zu verfolgen. Hierzu kann praktisch jeder beliebige Funktionsaufruf in ein Event verwandelt und aufgezeichnet werden. Die Aktivierung eines Tracers erfolgt über die Datei „current_tracer“, über diese kann auch geprüft werden, ob aktuell ein Tracer aktiv ist:
$ cat current_tracer
nop
Der Wert “nop” bedeutet, dass aktuell keiner der verfügbaren Tracer aktiviert ist. Um nun beispielsweise function tracing einzuschalten, muss folgendes Kommando ausgeführt werden:
$ echo function > current_tracer
Von nun an wird jeder einzelne Funktionsaufruf innerhalb des Betriebssystems als Event aufgezeichnet. Beim Auslesen des Ringbuffers über die Datei „trace“, sieht ein entsprechender Eintrag dann ungefähr wie folgt aus:
sh-79 [000] dn.. 358.947865: wakeup_preempt_entity <-pick_next_task_fair
Aufgezeichnet wurde in diesem Fall ein Aufruf von „wakeup_preempt_entity()“aus der Funktion „pick_next_task_fair()“. Ohne zusätzliche Konfigurationsschritte werden beim function tracing alle Funktionsaufrufe innerhalb des Betriebssystemkerns aufgezeichnet. Man kann sich leicht vorstellen, mit welcher Fülle an Events man es hier zu tun bekommt.
Bild 1 zeigt einen Ausschnitt aus einer entsprechenden Aufzeichnung. Die Analyse eines solchen Traces kann aufgrund der puren Datenmenge sehr schwierig sein. Darüber hinaus hat das Erzeugen einer großen Anzahl an Events einen nicht unwesentlichen Einfluss auf das Zeitverhalten des Systems. Aus diesem Grund stehen (so wie beim Event Tracing auch) diverse Möglichkeiten zur Konfiguration der Aufzeichnung und zum Filtern von Ereignissen zur Verfügung. So kann das Recording von Funktionen auf den Zeitraum der Ausführung einer bestimmten Applikation beschränkt werden. Dies erfolgt über die Datei „set_ftrace_pid“:
echo 79 > set_ftrace_pid
Obenstehendes Kommando führt dazu, dass die Aufzeichnung von Funktionsaufrufen fortan nur noch erfolgt, während der Prozess mit der PID 79 aktiv ist. Dies kann insbesondere bei der Analyse von Latenzproblemen in einem bestimmten Programm sehr hilfreich sein.
Selbstverständlich lässt sich die Aufzeichnung auch auf die Aufrufe bestimmter Funktionen begrenzen. Hierfür steht die Datei „set_ftrace_filter“ zur Verfügung. Ein Filter kann wahlweise explizit über den Funktionsnamen oder auch über Wildcards angelegt werden. Im Folgenden einige Beispiele:
# Aufzeichnung aller Aufrufe von add_wait_queue
$ echo add_wait_queue > set_ftrace_filter
# Erweitern des bestehenden Filters um alle Aufrufe, die mit irq_ beginnen
$ echo 'irq_*' >> set_ftrace_filter
# Anzeigen der gesetzten Filer:
$ cat set_ftrace_filter
irq_enter
irq_exit
irq_sysfs_add
irq_to_desc
[…]
Während „set_ftrace_filter” angibt, welche Funktionsaufrufe aufgezeichnet werden sollen, so kann über die Datei „set_ftrace_notrace“ umgekehrt angegeben werden, welche Aufrufe von der Aufzeichnung ausgeschlossen sind. Mit diesen Filtermethoden lässt sich die Datenmenge und der entsprechende zeitliche Einfluss auf das System wesentlich reduzieren. Mit dem function Tracer bietet die Linux Tracing Infrastruktur bereits eine sehr mächtige Möglichkeit, beliebige Codepfade aufzuzeichnen und nachzuverfolgen. In vielen Fällen kann aber neben den reinen Funktionsaufrufen noch eine weitere Information von Interesse sein, nämlich die Aufrufhierarchie. Und auch hierfür gibt es ein passendes Werkzeug: Den function_graph Tracer.
Aktiviert wird der function_graph Tracer analog zum vorhergehenden Beispiel:
$ echo function_graph > current_tracer
Hier sehen wir auch eine Einschränkung bei der Verwendung der Tracer. Es kann zu einem Zeitpunkt nur ein bestimmter Tracer aktiv sein. Vergleichen wir nun im Folgenden die Ergebnisse mit denen des function Tracers.
Bild 2 zeigt eine Aufzeichnung mit dem function_graph Tracer. Es ist sehr einfach zu erkennen, dass neben den Aufrufen der einzelnen Funktionen auch die Aufrufhierarchie gezeigt wird. Weiterhin ist über die Annotierung in der 2. Spalte die jeweilige Ausführungsdauer ersichtlich. Selbstverständlich kann auch hier wieder auf bestimmte Aufrufe gefiltert werden. Zusätzlich besteht noch die Möglichkeit eine sogenannte „graph function“ zu setzen, d.h. die Aufzeichnung wird automatisch gestartet, wenn eine bestimmte Funktion aufgerufen wird. Greifen wir das Beispiel aus Abbildung 2 auf und setzen die „graph function“ auf „arch_cpu_idle_enter()“.
$ echo arch_cpu_idle_enter > set_graph_function
Bild 3 veranschaulicht die Funktion von „set_graph_function“. Die Aufzeichnung wird nun automatisch mit arch_cpu_idle_enter() gestartet und auf die Ausführung dieser Funktion begrenzt. Analog zu den Filtern können auch beliebig viele „graph functions“ gesetzt werden.
Die hier gezeigten Features sind nur ein Bruchteil dessen, was mit den Tracern möglich ist. Weitere Funktionalitäten des function und des function_graph Tracers, sowie eine Beschreibung weiterer Tracer befindet sich in der Dokumentation des Linux Kernels unter: Documentation/trace/ftrace.txt.
trace_marker
Abläufe im Betriebssystem müssen sehr häufig mit Abläufen in einer Applikation korreliert werden. Insbesondere bei der Analyse von Latenzproblemen ist das Zusammenspiel eines Programmes mit dem Betriebssystem von sehr großem Interesse. Für diesen Zweck ist es notwendig, Trace Events auch aus einer Applikation heraus erzeugen zu können. Die Linux Tracing Infrastruktur bietet hierfür verschiedene Möglichkeiten, die einfachste davon ist die Verwendung der sogenannten Tracemarker.
Die Nutzung der Tracemarker gestaltet sich denkbar einfach. Bild 4 zeigt den grundsätzlichen Ablauf: Über die Datei „trace_marker“ kann ein beliebiger String in den Tracebuffer geschrieben werden. Diese Zeichenkette taucht bei der Analyse des Traces dann als einzelnes Event auf. Der Vorteil dieser Methode ist die äußerst einfache Anwendung, allerdings gibt es auch einen entscheidenden Nachteil. Der Overhead beim Schreiben eines Tracemarkers ist recht groß. Betrachten wir uns den Ablauf hierzu etwas genauer: Das Erzeugen des Events erfolgt durch das Schreiben auf eine virtuelle Datei. Hierzu muss diese geöffnet, geschrieben und wieder geschlossen werden. Das bedeutet, dass allein für das Schreiben eines einzigen Events drei Systemcalls erforderlich sind (open(), write() und close()).
Für einfache Analysen des Ablaufs einer Applikation oder des Zusammenspiels der Applikation mit einem Treiber ist dies durchaus akzeptabel, für zeitkritische Ereignisse oder für Events, die sehr häufig auftreten, ist diese Methode allerdings nicht geeignet. Daher gibt es noch eine weitere Möglichkeit, um Events aus einer Applikation heraus erzeugen zu können: Die sogenannten Uprobes.
Uprobes
Im ersten Teil dieser Artikelserie haben wir uns mit den Kprobes beschäftigt, mit denen sich dynamisch beliebige Events in das Betriebssystem einfügen lassen. Die Uprobes sind eine logische Weiterentwicklung dieser Technologie. Sie bieten die Möglichkeit Events zur Laufzeit in eine Applikation einzufügen. Die Aufzeichnung erfolgt analog zu allen anderen Events in die entsprechenden Ringbuffer. Wer zuvor bereits mit den Kprobes gearbeitet hat, dem wird die Verwendung der Uprobes sehr leichtfallen, denn die Konfiguration lehnt sich sehr stark an die der Kprobes an. Betrachten wir dies an einem einfachen Beispiel:
Gehen wir davon aus wir möchten in der Beispielapplikation aus Abbildung 6 ein Event in Zeile 8 einfügen. Um ein entsprechendes Uprobe definieren zu können ist ein wenig Vorarbeit notwendig. Bei der Verwendung von Kprobes können wir einfach einen Symbolnamen spezifizieren. Da jede Applikation aber über einen eigenen virtuelle Adressraum und somit über eine andere Adressbasis verfügt, wird für das Erzeugen eines Uprobes das Adressoffest im Textsegment der jeweiligen Applikation benötigt. Dies kann zum Beispiel mit objdump ermittelt werden (das Executable wurde mit Debugsymbolen erzeugt):
$ objdump -F -S -D hello | less
In Bild 6 sind die Ausgaben von objdump zu sehen. Daraus ergibt sich für die gewünschte Codezeile ein Offset von 0x378. Mit dieser Information kann nun bereits ein Uprobe generiert werden, hierfür steht die Datei uprobe_events zur Verfügung:
$ echo "p:my_uprobe /path_to_application/hello:0x378" > uprobe_events
Es ist leicht zu erkennen, dass die verwendete Syntax sehr dem Erzeugen eines Kprobes ähnelt. Anstelle des Symbolnamens wird hier nun das Executable (mit vollem Pfad) und mit einem Doppelpunkt getrennt das Offset angegeben. Das somit erzeugte Event befindet sich unter events/uprobes und lässt sich wie folgt aktivieren:
$ echo 1 > events/uprobes/my_uprobe/enable
Beim nächsten Aufruf der Applikation wird dieses Event entsprechend aufgezeichnet:
# Ausführen der Applikation
$ /path_to_application/hello
Bild 7 zeigt die aufgezeichneten Events. Im Vergleich zu den im vorangegangenen Kapitel vorgestellten Tracemarkern ist diese Methode mit wesentlich geringerem Overhead verbunden und somit auch für zeitkritische Ereignisse geeignet. Eine ausführliche Dokumentation zu den Uprobes und deren Möglichkeiten findet sich ebenfalls in der Dokumentation des Linux Kernels und zwar unter: Documentation/trace/uprobetracer.txt
Fazit
Wir haben uns in dieser 3-teiligen Artikelserie mit den in Linux verfügbaren Boardmitteln für Tracing beschäftigt. Mit der Tracing Infrastruktur bietet Linux von Hause aus alles, was zur Analyse von komplexen Fehlerbildern innerhalb des Betriebssystems oder im Zusammenhang mit dem Betriebssystem benötigt wird. Es besteht nicht nur die Möglichkeit einzelne Events aufzuzeichnen, sondern Ereignisse können auch dynamisch zur Laufzeit erzeugt und verwendet werden. Mit den Tracern bietet Linux darüber hinaus noch vorgefertigte Heuristiken, die bei der Analyse bestimmter Szenarien unterstützen. Und mit den Tracemarkern und den Uprobes können Vorgänge innerhalb des Betriebssystemkerns sehr einfach mit Abläufen in der Applikation korreliert werden.
:quality(80)/p7i.vogel.de/wcms/54/ed/54edd8c4cd1597dc3a16436844d92b32/90781069.jpeg)
Einführung in die Linux Tracing Infrastruktur
:quality(80)/p7i.vogel.de/wcms/3e/5f/3e5f2deff86c207270934bca1e5ea830/91733714.jpeg)
Tools für einfacheres und effizienteres Tracing unter Linux
* Jan Altenberg beschäftigt sich seit mehr als 15 Jahren beruflich mit Linux. Er betreute u.a. verschiedene Arbeiten für das EU-Projekt OCEAN, welches sich zum Ziel setzte, eine offene Steuerungsplattform auf Basis von Realtime Linux zu schaffen. Weiterhin trug er bei einem namhaften Maschinenhersteller die volle konzeptionelle Verantwortung für die Einführung von Linux in der neuen Steuerungsgeneration. Von 2007-2019 arbeitete er für die Linutronix GmbH und leitete dort zuletzt den technischen Vertrieb. Jan Altenberg ist regelmäßiger Sprecher auf verschiedenen Fachkonferenzen und wurde 2014, 2018 und 2019 von den Besuchern des ESE Kongress mit dem Speaker Award Publikumspreis ausgezeichnet. Seit April 2019 arbeitet er als System Architekt und Experte für Open-Source Technologien für die Continental Automotive GmbH.
(ID:47041908)