`systemtap-toolkit` und `stapxx`: Wie man Daten nutzt, um schwierige Probleme zu lösen?

API7.ai

December 22, 2022

OpenResty (NGINX + Lua)

Wie im vorherigen Artikel vorgestellt, vertiefen wir uns als Server-seitige Entwicklungsingenieure nicht in dynamische Debugging-Toolsets, sondern bleiben meist auf der Nutzungsebene und schreiben höchstens einige einfache Standard-Skripte. Die tieferen Ebenen, wie CPU-Cache, Architektur, Compiler usw., sind das Gebiet von Performance-Ingenieuren.

Es gibt zwei Open-Source-Projekte in OpenResty: openresty-systemtap-toolkit und stapxx. Es handelt sich um systemtap-basierte, verpackte Toolsets zur Echtzeitanalyse und Diagnose von NGINX und OpenResty. Sie decken on-CPU, off-CPU, Shared Dictionary, Garbage Collection, Request-Latenz, Memory-Pooling, Connection-Pooling, Dateizugriff und andere gängige Funktionen und Debugging-Szenarien ab.

In diesem Artikel werden wir diese Tools und ihre entsprechenden Anwendungen durchgehen, um uns zu helfen, schnell Werkzeuge zur Problembehebung zu finden, wenn wir auf Fehlerbehebungsprobleme mit NGINX und OpenResty stoßen. In der Welt von OpenResty ist das Erlernen der Verwendung dieser Tools ein sicherer Weg, um voranzukommen, und eine sehr effektive Möglichkeit, mit anderen Entwicklern zu kommunizieren – schließlich sind die von den Tools generierten Daten genauer und detaillierter, als wir sie in Worten beschreiben könnten.

Es ist jedoch wichtig zu beachten, dass der LuaJIT GC64-Modus standardmäßig in OpenResty Version 1.15.8 aktiviert ist, aber openresty-systemtap-toolkit und stapxx die entsprechenden Änderungen nicht nachvollziehen, was dazu führt, dass die Tools darin nicht korrekt funktionieren. Daher sollten wir diese Tools besser in der alten 1.13 Version von OpenResty verwenden.

Die meisten Open-Source-Projektmitwirkenden sind Teilzeitkräfte und nicht verpflichtet, die Tools funktionsfähig zu halten, was wir bei der Verwendung von Open-Source-Projekten beachten müssen.

Beispiel: shared dict

Beginnen wir mit einem Tool, das wir wahrscheinlich am besten kennen und am einfachsten verwenden können, ngx-lua-shdict, als Beispiel, um den heutigen Beitrag zu starten.

ngx-lua-shdict ist ein Tool, das die shared dicts von NGINX analysiert und ihre Operationen verfolgt. Sie können die -f-Option verwenden, um das dict und den key anzugeben, um die Daten im shared dict zu erhalten. Die --raw-Option ermöglicht es Ihnen, den Rohwert des angegebenen key zu exportieren.

Das Folgende ist ein Befehlszeilenbeispiel, um Daten aus einem shared dict zu erhalten.

# Angenommen, die NGINX Worker PID ist 5050
$ ./ngx-lua-shdict -p 5050 -f --dict dogs --key Jim --luajit20
Tracing 5050 (/opt/nginx/sbin/nginx)...

type: LUA_TBOOLEAN
value: true
expires: 1372719243270
flags: 0xa

Ebenso können wir die -w-Option verwenden, um die Schreibvorgänge im dict für einen bestimmten key zu verfolgen:

$./ngx-lua-shdict -p 5050 -w --key Jim --luajit20
Tracing 5050 (/opt/nginx/sbin/nginx)...

Hit Ctrl-C to end

set Jim exptime=4626322717216342016
replace Jim exptime=4626322717216342016
^C

Sehen wir uns an, wie dieses Tool implementiert ist. ngx-lua-shdict ist ein Perl-Skript, aber die Implementierung hat nichts mit Perl zu tun, das nur verwendet wird, um das stap-Skript zu generieren und auszuführen.

open my $in, "|stap $stap_args -x $pid -" or die "Cannot run stap: $!\n";

Wir könnten es in Python, PHP, Go oder jeder anderen Sprache schreiben, die wir mögen. Der entscheidende Punkt im stap-Skript ist die folgende Codezeile:

probe process("$nginx_path").function("ngx_http_lua_shdict_set_helper")

Dies ist die probe, die wir zuvor erwähnt haben, die die Funktion ngx_http_lua_shdict_set_helper abtastet. Die Aufrufe dieser Funktion befinden sich alle in der Datei lua-nginx-module/src/ngx_http_lua_shdict.c des lua-nginx-module-Moduls.

static int
ngx_http_lua_shdict_add(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_ADD);
}

static int
ngx_http_lua_shdict_safe_add(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_ADD
|NGX_HTTP_LUA_SHDICT_SAFE_STORE);
}

static int
ngx_http_lua_shdict_replace(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_REPLACE);
}

Auf diese Weise können wir alle Operationen des Shared Dict verfolgen, indem wir einfach diese Funktion abtasten.

on-CPU, off-CPU

Bei der Verwendung von OpenResty ist das häufigste Problem, auf das wir stoßen, die Leistung. Es gibt zwei Haupttypen von schlechter Leistung, d.h. niedrige QPS: zu hohe CPU-Auslastung und zu niedrige CPU-Auslastung. Der erstgenannte Engpass kann darauf zurückzuführen sein, dass wir die zuvor vorgestellten Leistungsoptimierungsmethoden nicht verwenden, während der letztgenannte auf blockierende Funktionen zurückzuführen sein kann. Die entsprechenden on-CPU- und off-CPU-Flame-Graphs können uns helfen, die ultimative Ursache zu identifizieren.

Um C-Level on-CPU-Flame-Graphs zu generieren, müssen Sie sample-bt in systemtap-toolkit verwenden; während Lua-Level on-CPU-Flame-Graphs von lj-lua-stacks in stapxx generiert werden.

Nehmen wir sample-bt als Beispiel, wie man es verwendet. sample-bt ist ein Skript, das den Aufrufstapel eines beliebigen Benutzerprozesses, den wir angeben (nicht nur NGINX- und OpenResty-Prozesse), abtastet.

Zum Beispiel können wir einen laufenden NGINX Worker-Prozess (mit einer PID von 8736) für 5 Sekunden mit dem folgenden Code abtasten:

$ ./sample-bt -p 8736 -t 5 -u > a.bt
WARNING: Tracing 8736 (/opt/nginx/sbin/nginx) in user-space only...
WARNING: Missing unwind data for module, rerun with 'stap -d stap_df60590ce8827444bfebaf5ea938b5a_11577'
WARNING: Time's up. Quitting now...(it may take a while)
WARNING: Number of errors: 0, skipped probes: 24

Es gibt die Ergebnisdatei a.bt aus, die verwendet werden kann, um mit dem FlameGraph-Toolset einen Flame-Graph zu generieren:

stackcollapse-stap.pl a.bt > a.cbt
flamegraph.pl a.cbt > a.svg

Die a.svg ist der generierte Flame-Graph, den wir im Browser öffnen können, um ihn anzuzeigen. Während des Abtastzeitraums müssen wir jedoch eine bestimmte Anzahl von Anfragen aufrechterhalten. Andernfalls gibt es keine Möglichkeit, den Flame-Graph zu generieren, wenn die Stichprobenanzahl 0 ist.

Als nächstes schauen wir uns an, wie man off-CPU abtastet, indem wir das Skript sample-bt-off-cpu in systemtap-toolkit verwenden, das ähnlich wie sample-bt ist, wie folgt:

$ ./sample-bt-off-cpu -p 10901 -t 5 > a.bt
WARNING: Tracing 10901 (/opt/nginx/sbin/nginx)...
WARNING: _stp_read_address failed to access memory location
WARNING: Time's up. Quitting now...(it may take a while)
WARNING: Number of errors: 0, skipped probes: 23

In stapxx ist das Tool zur Analyse der Latenz epoll-loop-blocking-distr, das den angegebenen Benutzerprozess abtastet und die Latenzverteilung zwischen aufeinanderfolgenden epoll_wait-Systemaufrufen ausgibt.

$ ./samples/epoll-loop-blocking-distr.sxx -x 19647 --arg time=60
Start tracing 19647...
Please wait for 60 seconds.
Distribution of epoll loop blocking latencies (in milliseconds)
max/avg/min: 1097/0/0
value |-------------------------------------------------- count
    0 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  18471
    1 |@@@@@@@@                                            3273
    2 |@                                                    473
    4 |                                                     119
    8 |                                                      67
   16 |                                                      51
   32 |                                                      35
   64 |                                                      20
  128 |                                                      23
  256 |                                                       9
  512 |                                                       2
 1024 |                                                       2
 2048 |                                                       0
 4096 |                                                       0

Wie wir sehen können, zeigt diese Ausgabe, dass die überwiegende Mehrheit der Verzögerungen weniger als 1 ms beträgt, aber es gibt einige, die über 200 ms liegen, und diese sind diejenigen, die zu beachten sind.

Upstream und Phase-Tracking

Zusätzlich zu Leistungsproblemen, die im Code von OpenResty selbst auftreten können, kann es, wenn OpenResty über Upstream-Module wie cosocket oder proxy_pass mit Upstream-Diensten kommuniziert, auch erhebliche Auswirkungen auf die Gesamtleistung haben, wenn der Upstream-Dienst selbst eine große Latenz aufweist.

In diesem Fall können wir die Tools ngx-lua-tcp-recv-time, ngx-lua-udp-recv-time und ngx-single-req-latency zur Analyse verwenden. Hier ist ein Beispiel für ngx-single-req-latency.

Dieses Tool unterscheidet sich etwas von den meisten Tools innerhalb des Toolsets. Die meisten anderen Tools basieren auf einer großen Anzahl von Stichproben und statistischen Analysen, um eine mathematische Schlussfolgerung über die Verteilung zu ziehen. Stattdessen analysiert ngx-single-req-latency einzelne Anfragen und verfolgt die Zeit, die für einzelne Anfragen in verschiedenen Phasen in OpenResty aufgewendet wird, wie z.B. die rewrite-, access- und content-Phasen sowie Upstream.

Sehen wir uns ein konkretes Beispielcode an:

# making the ./stap++ tool visible in PATH:
$ export PATH=$PWD:$PATH

# assuming an nginx worker process's pid is 27327
$ ./samples/ngx-single-req-latency.sxx -x 27327
Start tracing process 27327 (/opt/nginx/sbin/nginx)...

POST /api_json
    total: 143596us, accept() ~ header-read: 43048us, rewrite: 8us, pre-access: 7us, access: 6us, content: 100507us
    upstream: connect=29us, time-to-first-byte=99157us, read=103us

$ ./samples/ngx-single-req-latency.sxx -x 27327
Start tracing process 27327 (/opt/nginx/sbin/nginx)...

GET /robots.txt
    total: 61198us, accept() ~ header-read: 33410us, rewrite: 7us, pre-access: 7us, access: 5us, content: 27750us
    upstream: connect=30us, time-to-first-byte=18955us, read=96us

Dieses Tool verfolgt die erste Anfrage, auf die es nach dem Start stößt. Die Ausgabe ähnelt opentracing sehr, und wir können uns systemtap-toolkit und stapxx sogar als nicht-invasive Versionen von APM (Application Performance Management) in OpenResty vorstellen.

Zusammenfassung

Zusätzlich zu den gängigen Tools, über die ich heute gesprochen habe, bietet OpenResty natürlich noch viele weitere Tools, die Sie selbst erkunden und erlernen können.

Es gibt noch einen signifikanten Unterschied zwischen OpenResty und anderen Entwicklungssprachen und Plattformen in Bezug auf Tracing-Technologie, den wir langsam schätzen können.

Halten Sie den Code sauber und stabil, fügen Sie keine Proben hinzu, sondern nehmen Sie ihn durch externe dynamische Tracking-Techniken auf.

Welche Tools haben Sie verwendet, um Probleme bei der Verwendung von OpenResty zu verfolgen und zu analysieren? Sie sind herzlich eingeladen, diesen Artikel zu teilen, und wir werden gemeinsam teilen und Fortschritte machen.