diff --git a/README.markdown b/README.markdown index 1251143..4355348 100644 --- a/README.markdown +++ b/README.markdown @@ -1023,6 +1023,42 @@ Successfully tested on Linux kernel 3.7 and should work for other versions of ke This tool requires a Linux kernel compiled by gcc 4.5+ (preferrably gcc 4.7+) because gcc versions older than 4.5 generated incomplete DWARF debug info for C inlined functions. It is also recommended to enable DWARF format version 3 or above when compiling the kernel (by passing the `-gdwarf-3` or `-gdwarf-4` option to the `gcc` command line). +ngx-lua-shdict +------- + +This tool analyzes shared memory dict and tracks dict operations in the specified running nginx process. + +You can specify the `-f` option to fetch the data from the shared memory dict name by the specified dict and key. +Specify the `--raw` option when you need dump the raw value of the given key. + +Specify the `--lua51` option when you're using the standard Lua 5.1 interpreter in your Nginx build, or `--luajit20` if LuaJIT 2.0 is used instead. Currently only LuaJIT is supported. + +Here's a sample command to fetch the data from the shared memory dict: + + # assuming the nginx worker pid is 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 + + 6 microseconds elapsed in the probe handler. + +Similarly, you can specify the `-w` option to track dict writes for the given key: + + $./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 + +If you don't specify `-f` or `-w`, this tool will fetch the data by default. + ngx-lua-conn-pools ---------------- diff --git a/ngx-lua-shdict b/ngx-lua-shdict new file mode 100755 index 0000000..0253b98 --- /dev/null +++ b/ngx-lua-shdict @@ -0,0 +1,525 @@ +#!/usr/bin/env perl + +# Copyright (C) Yichun Zhang (agentzh) +# Copyright (C) Guanlan Dai + +use 5.006001; +use strict; +use warnings; + +use Getopt::Long qw( GetOptions ); + +GetOptions("a=s", \(my $stap_args), + "d", \(my $dump_src), + "h", \(my $help), + "lua51", \(my $lua51), + "luajit20", \(my $luajit20), + "w", \(my $trace_write), + "f", \(my $find_value), + "p=i", \(my $pid), + "raw", \(my $raw), + "dict=s", \(my $dict), + "key=s", \(my $key)) + or die usage(); + +if ($help) { + print usage(); + exit; +} + +if (!$pid) { + die "No nginx process pid specified by the -p option\n"; +} + +if (!defined $stap_args) { + $stap_args = ''; +} + +if ($stap_args !~ /\b-D\s*MAXACTION=/) { + $stap_args .= " -DMAXACTION=100000" +} + +if ($stap_args !~ /\b-D\s*MAXMAPENTRIES=/) { + $stap_args .= " -DMAXMAPENTRIES=5000" +} + +if ($stap_args !~ /\b-D\s*MAXBACKTRACE=/) { + $stap_args .= " -DMAXBACKTRACE=200" +} + +if ($stap_args !~ /\b-D\s*MAXSTRINGLEN=2048/) { + $stap_args .= " -DMAXSTRINGLEN=2048" +} + +if ($stap_args !~ /\b-D\s*MAXSKIPPED=1024/) { + $stap_args .= " -DMAXSKIPPED=1024" +} + +if ($^O ne 'linux') { + die "Only linux is supported but I am on $^O.\n"; +} + +my $nginx_file = "/proc/$pid/exe"; +if (!-f $nginx_file) { + die "Nginx process $pid is not running or ", + "you do not have enough permissions.\n"; +} + +my $nginx_path = readlink $nginx_file; +my $lua_path = get_lua_path(); + +my $ver = `stap --version 2>&1`; +if (!defined $ver) { + die "Systemtap not installed or its \"stap\" utility is not visible to the PATH environment: $!\n"; +} + +if ($ver =~ /version\s+(\d+\.\d+)/i) { + my $v = $1; + if ($v < 2.1) { + die "ERROR: at least systemtap 2.1 is required but found $v\n"; + } + +} else { + die "ERROR: unknown version of systemtap:\n$ver\n"; +} +if (!$key) { + die "ERROR: please specify a key\n"; +} + +my $stap_src; +my $preamble; + +if ($trace_write) { + if ($dict) { + die "ERROR: specify a dict when tracing writes is not supported\n"; + } + my $o = qq{\@cast(o, "TValue", "$lua_path")}; + my $L = qq{\@cast(L, "lua_State", "$lua_path")}; + my $gcr = qq{\@cast(gcr, "GCRef", "$lua_path")}; + my $gcobj = qq{\@cast(gcobj, "GCobj", "$lua_path")}; + my $str = qq{\@cast(str, "GCstr", "$lua_path")}; + my $sizeof_TValue = qq{\&\@cast(0, "TValue", "$lua_path")[1]}; + my $sizeof_GCstr = qq{\&\@cast(0, "GCstr", "$lua_path")[1]}; + my $TL_TSTR = "4294967291"; + my $NGX_HTTP_LUA_SHDICT_ADD = '0x0001'; + my $NGX_HTTP_LUA_SHDICT_REPLACE = '0x0002'; + my $NGX_HTTP_LUA_SHDICT_SAFE_STORE = '0x0004'; + my $LJ_TISNUM = '0xfffeffffu'; + $preamble = <<_EOC_; +probe begin { + printf("Tracing %d ($nginx_path)...\\n\\n", target()) + printf("Hit Ctrl-C to end.\\n\\n") +} +_EOC_ + + $stap_src = <<_EOC_; +$preamble + +function gcref(gcr) +{ + return $gcr->gcptr32 +} + +function gcval(o) +{ + return gcref(\&$o->gcr) +} + +function strdata(s) +{ + return s + $sizeof_GCstr +} + +probe process("$nginx_path").function("ngx_http_lua_shdict_set_helper") +{ + if (pid() == target()) { + flags = \$flags + if (flags & $NGX_HTTP_LUA_SHDICT_ADD) { + op = "add" + + } else if (flags & $NGX_HTTP_LUA_SHDICT_REPLACE) { + op = "replace" + + } else { + op = "set" + } + + if (flags & $NGX_HTTP_LUA_SHDICT_SAFE_STORE) { + op = "safe_" . op + } + + L = \$L + o = $L->base + $sizeof_TValue * (2 - 1) + if (o < $L->top && $o->it == $TL_TSTR) { + gcobj = gcval(o) + str = \&$gcobj->str + //printf("gmatch regex: %s\\n", user_string_n(strdata(str), $str->len)) + key = user_string_n(strdata(str), $str->len) + if (key == "$key") { + printf("%s %s", op, key) + + o = $L->base + $sizeof_TValue * (4 - 1) + if (o < $L->top) { + exptime = user_long(&$o->n) + printf(" exptime=%d", exptime) + } + + printf("\\n") + } + } + } +} +_EOC_ + +} else { + if (!$dict) { + die "ERROR: please specify a dict zone\n"; + } + + my $hash = crc32($key); + my $dict_name_len = length $dict; + my $key_len = length $key; + my $quoted_zone = quote_str($dict); + my $LUA_TBOOLEAN = 1; + my $LUA_TNUMBER = 3; + my $LUA_TSTRING = 4; + my $part = qq{\@cast(part, "ngx_list_part_t")}; + my $node = qq{\@cast(node, "ngx_rbtree_node_t", "$nginx_path")}; + my $sd = qq{\@cast(sd, "ngx_http_lua_shdict_node_t", "$nginx_path")}; + my $sh = qq{\@cast(sh, "ngx_http_lua_shdict_shctx_t", "$nginx_path")}; + + my $print_result_func_def; + my $print_time_elasped_func_def; + my $set_timer_begin; + + if ($raw) { + $preamble = ""; + + $set_timer_begin = ""; + + $print_time_elasped_func_def = ""; + + $print_result_func_def = <<_EOC_; +function print_result(sd) { + if (sd) { + value_type = $sd->value_type; + data = $sd->data + $key_len; + + if (value_type == $LUA_TSTRING) { + print(user_string(data)) + + } else if (value_type == $LUA_TNUMBER) { + print(user_long(data)) + + } else if (value_type == $LUA_TBOOLEAN) { + print(user_long(data)) + + } else { + printf("ERROR: bad value type found for key $key in shared_dict $dict\\n") + } + + } else { + exit() + } +} +_EOC_ + + } else { + $preamble = <<_EOC_; +probe begin { + printf("Tracing %d ($nginx_path)...\\n\\n", target()) +} +_EOC_ + + $set_timer_begin = qq{begin = local_clock_us()}; + + $print_time_elasped_func_def = <<_EOC_; +elapsed = local_clock_us() - begin +printf("\\n%d microseconds elapsed in the probe handler.\\n", elapsed) +_EOC_ + + $print_result_func_def = <<_EOC_; +function print_result(sd) { + if (sd) { + value_type = $sd->value_type; + data = $sd->data + $key_len; + if (value_type == $LUA_TSTRING) { + printf("type: LUA_TSTRING\\n") + printf("value: %s\\n", text_str(user_string(data))) + printf("expires: %d\\n", $sd->expires) + printf("flags: 0x%x\\n", $sd->user_flags) + + } else if (value_type == $LUA_TNUMBER) { + printf("type: LUA_TNUMBER\\n") + printf("value: %d\\n", user_long(data)) + printf("expires: %d\\n", $sd->expires) + printf("flags: 0x%x\\n", $sd->user_flags) + + } else if (value_type == $LUA_TBOOLEAN) { + printf("type: LUA_TBOOLEAN\\n") + if ( user_long(data) == 0) { + printf("value: false\\n") + + } else if ( user_long(data) == 1) { + printf("value: true\\n") + + } else { + printf("ERROR: value error\\n") + } + + printf("expires: %d\\n", $sd->expires) + printf("flags: 0x%x\\n", $sd->user_flags) + + } else { + error("Bad value type found for key $key in shared_dict $dict\\n") + } + + } else { + printf("ERROR: key not found\\n"); + } +} +_EOC_ + + } + chop $preamble; + + $stap_src = <<_EOC_; +$preamble + +function ngx_http_lua_shdict_lookup(zone, hash, key) { + ctx = \@cast(zone, "ngx_shm_zone_t", "$nginx_path")->data + sh = \@cast(ctx, "ngx_http_lua_shdict_ctx_t", "$nginx_path")->sh + node = $sh->rbtree->root + sentinel = $sh->rbtree->sentinel + + while (node != sentinel) { + + if (hash < $node->key) { + node = $node->left + continue + } + + if (hash > $node->key) { + node = $node->right + continue + } + + sd = &$node->color + data = $sd->data + data_str = text_str(user_string(data)) + data_key = substr(data_str, 0 , $key_len) + + if (data_key == "$key"){ + return sd + + } else { + if (data_key < "$key") { + node = $node->left + + } else { + node = $node->right + } + } + } + return 0 +} + +$print_result_func_def + +probe process("$nginx_path").function("ngx_process_events_and_timers"), + process("$nginx_path").function("ngx_http_init_request")!, + process("$nginx_path").function("ngx_http_init_connection") +{ + if (pid() == target()) { + + zone_found = 0 + + $set_timer_begin + + part = &\@var("ngx_cycle\@ngx_cycle.c")->shared_memory->part + zone = $part->elts + + for (i = 0; ; i++) { + + if (i >= $part->nelts) { + if ($part->next == 0) { + break + } + + part = $part->next + zone = $part->elts + i = 0 + } + + shm = &\@cast(zone, "ngx_shm_zone_t")[i]->shm + name = &\@cast(shm, "ngx_shm_t")->name + + if (\@cast(name, "ngx_str_t")->len != $dict_name_len) { + continue; + } + + zone_name = user_string_n(\@cast(name, "ngx_str_t")->data, $dict_name_len) + if (zone_name != $quoted_zone) { + continue; + } + + sd = ngx_http_lua_shdict_lookup(zone, $hash, "$key") + print_result(sd) + + zone_found = 1 + break + } + + if (!zone_found) { + printf("dict \\"%s\\" not found.\\n", $quoted_zone) + } + + $print_time_elasped_func_def + + exit() + + } /* pid() == target() */ +} +_EOC_ + +} + +if ($dump_src) { + print $stap_src; + exit; +} + +open my $in, "|stap $stap_args -x $pid -" + or die "Cannot run stap: $!\n"; + +print $in $stap_src; + +close $in; + +sub usage { + return <<'_EOC_'; +Usage: + ngx-lua-shdict [optoins] + +Options: + -p Specify the nginx worker process pid. + -a Pass extra arguments to the stap utility. + -d Dump out the systemtap script source. + -f Find value of specify key. + -w Trace writes to the dict. + --lua51 The target Nginx is using the standard Lua 5.1 interpreter. + --luajit20 The target Nginx is using the LuaJIT 2.0. + --dict Specify the dict. + --key Specify the key. + --raw Raw output. + +Examples: + ngx-lua-shdict -p 12345 + ngx-lua-shdict -p 12345 -f --dict dogs --key Jim --luajit20 + ngx-lua-shdict -p 12345 -f --dict dogs --key Jim --luajit20 --raw + ngx-lua-shdict -p 12345 -w --key Jim --luajit +_EOC_ +} + +sub quote_str { + my $s = shift; + $s =~ s/\\/\\\\/g; + $s =~ s/"/\\"/g; + $s =~ s/\n/\\n/g; + $s =~ s/\t/\\t/g; + $s =~ s/\r/\\r/g; + return qq{"$s"}; +} + +sub get_lua_path { + my $lua_path; + + if (!defined $lua51 && !defined $luajit20) { + die "Neither --lua51 nor --luajit20 options are specified.\n"; + } + + my $maps_file = "/proc/$pid/maps"; + open my $in, $maps_file + or die "Cannot open $maps_file for reading: $!\n"; + + while (<$in>) { + if (m{\S+\bliblua-(\d+\.\d+)\.so(?:\.\d+)*$}) { + my ($path, $ver) = ($&, $1); + + if ($luajit20) { + die "The --luajit20 option is specified but seen standard Lua library: $path\n"; + } + + if ($ver ne '5.1') { + die "Nginx server $pid uses a Lua $ver library ", + "but only Lua 5.1 is supported.\n"; + } + + $lua_path = $path; + last; + + } elsif (m{\S+\bliblua\.so(?:\.\d+)*$}) { + my $path = $&; + + if ($luajit20) { + die "The --luajit20 option is specified but seen standard Lua library: $path\n"; + } + + $lua_path = $path; + last; + + } elsif (m{\S+\blibluajit-(\d+\.\d+)\.so(?:\.\d+)*$}) { + my ($path, $ver) = ($&, $1); + + if ($lua51) { + die "The --lua51 option is specified but seen the LuaJIT library: $path\n"; + } + + if ($ver ne '5.1') { + die "Nginx server $pid uses a Lua $ver compatible LuaJIT library ", + "but only Lua 5.1 is supported.\n"; + } + + $lua_path = $path; + last; + } + } + + close $in; + + if (!defined $lua_path) { + #warn "FALL BACK TO NGINX PATH"; + $lua_path = $nginx_path; + } + + return $lua_path; +} + +sub crc32 { + my ($input, $init_value, $polynomial) = @_; + + $init_value = 0 unless (defined $init_value); + $polynomial = 0xedb88320 unless (defined $polynomial); + + my @lookup_table; + + for (my $i = 0; $i < 256; $i++) { + my $x = $i; + for (my $j = 0; $j < 8; $j++) { + if ($x & 1) { + $x = ($x >> 1) ^ $polynomial; + + } else { + $x = $x >> 1; + } + } + push @lookup_table, $x; + } + my $crc = $init_value ^ 0xffffffff; + foreach my $x (unpack ('C*', $input)) { + $crc = (($crc >> 8) & 0xffffff) ^ $lookup_table[ ($crc ^ $x) & 0xff ]; + } + $crc = $crc ^ 0xffffffff; + + return $crc; +}