├── LICENSE ├── README.md ├── common.lua ├── init.lua ├── log.lua ├── nginx.conf └── show.lua /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Studio Sol Comunidação Digital Ltda. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms are permitted 5 | provided that the above copyright notice and this paragraph are 6 | duplicated in all such forms and that any documentation, 7 | advertising materials, and other materials related to such 8 | distribution and use acknowledge that the software was developed 9 | by the Studio Sol. The name of the 10 | Studio Sol may not be used to endorse or promote products derived 11 | from this software without specific prior written permission. 12 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 13 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngx_stats 2 | 3 | Watch live nginx stats from your servers 4 | 5 | 6 | ## Installation 7 | 8 | 1. Install [nginx](http://nginx.org/) configured with [--add-module=/path/to/lua-nginx-module](https://github.com/chaoslawful/lua-nginx-module) 9 | 2. Clone [ngx_stats](https://github.com/StudioSol/ngx_stats) in path specified by ```lua_package_path``` policy 10 | 11 | ```sh 12 | cd /etc/nginx/lua/ 13 | git clone git@github.com:StudioSol/ngx_stats.git stats 14 | ``` 15 | 16 | ## Overview 17 | 18 | Nginx Lua module to capture and show stats. 19 | ```stats/log.lua``` module collect statistics for requests across location with ```log_by_lua_file 'stats/log.lua';``` directive. 20 | ```stats/show.lua``` response for JSON reply. 21 | 22 | 23 | 24 | 25 | ## Configure nginx: 26 | 27 | ```nginx 28 | user www-data; 29 | worker_processes 1; 30 | error_log /var/log/nginx/error.log error; 31 | pid /var/run/nginx.pid; 32 | events { 33 | worker_connections 61440; 34 | } 35 | 36 | http { 37 | access_log off; 38 | 39 | lua_shared_dict ngx_stats 10m; 40 | lua_package_path '/etc/nginx/lua/?.lua;;'; 41 | lua_package_cpath '/etc/nginx/lua/?.so;;'; 42 | init_by_lua_file /etc/nginx/lua/stats/init.lua; 43 | 44 | proxy_cache_path /var/tmp/cache_http levels=1:1:1 45 | keys_zone=g_cache:64m 46 | max_size=1024m inactive=4h; 47 | 48 | server { 49 | listen 80; 50 | server_name example.com; 51 | set $stats_group "example.com"; 52 | log_by_lua_file /etc/nginx/lua/stats/log.lua; 53 | location / { 54 | proxy_pass "http://127.0.0.1:8080"; 55 | proxy_cache g_cache; 56 | } 57 | location = /status.json { 58 | default_type 'application/json'; 59 | content_by_lua_file /etc/nginx/lua/stats/show.lua; 60 | } 61 | } 62 | server { 63 | listen 80; 64 | server_name sub.example.com; 65 | set $stats_group "sub.example.com"; 66 | log_by_lua_file /etc/nginx/lua/stats/log.lua; 67 | location / { 68 | proxy_pass "http://127.0.0.1:8081"; 69 | proxy_cache g_cache; 70 | } 71 | } 72 | server { 73 | listen 80 default_server; 74 | server_name _; 75 | log_by_lua_file /etc/nginx/lua/stats/log.lua; 76 | return 404; 77 | } 78 | } 79 | 80 | ``` 81 | ## Result 82 | 83 | 84 | - See result in ```example.com/status.json``` 85 | - All requests that do not have ```$stats_group```, are displayed in the group: ```other``` 86 | 87 | ```json 88 | { 89 | "stats_start": 1397069333.819, 90 | "example.com": { 91 | "cache": { 92 | "expired": 812415, 93 | "updating": 1526, 94 | "miss": 13276642, 95 | "hit": 24260426, 96 | "stale": 162 97 | }, 98 | "upstream_requests_total": 14090608, 99 | "request_time": { 100 | "sum": 803899.9091289 101 | }, 102 | "status": { 103 | "3xx": 2, 104 | "4xx": 238784, 105 | "5xx": 18619, 106 | "2xx": 38109854 107 | }, 108 | "upstream_resp_time_sum": 636378.56698991, 109 | "requests_total": 38367259 110 | }, 111 | "sub.example.com": { 112 | "cache": { 113 | "expired": 911851, 114 | "updating": 1187, 115 | "miss": 1742453, 116 | "hit": 61705830, 117 | "stale": 1061 118 | }, 119 | "upstream_requests_total": 2657158, 120 | "request_time": { 121 | "sum": 1607474.1242547 122 | }, 123 | "status": { 124 | "3xx": 538834, 125 | "4xx": 1080163, 126 | "5xx": 1693, 127 | "2xx": 62743490 128 | }, 129 | "upstream_resp_time_sum":105318.85199993, 130 | "requests_total": 64364180 131 | }, 132 | "other": { 133 | "status": { 134 | "4xx":4169 135 | }, 136 | "requests_total": 4169, 137 | "request_time": { 138 | "sum": 51761.917998552 139 | } 140 | } 141 | } 142 | ``` 143 | 144 | ## See Also 145 | 146 | - [lua-nginx-module](https://github.com/chaoslawful/lua-nginx-module) Embed the Power of Lua into NginX 147 | - [LuaJIT](http://luajit.org/) a Just-In-Time Compiler for Lua 148 | - [nginx](http://nginx.org/) HTTP and reverse proxy server 149 | -------------------------------------------------------------------------------- /common.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local function split(str, pat) 4 | local t = {} -- NOTE: use {n = 0} in Lua-5.0 5 | local fpat = "(.-)" .. pat 6 | local last_end = 1 7 | local s, e, cap = str:find(fpat, 1) 8 | while s do 9 | if s ~= 1 or cap ~= "" then 10 | table.insert(t,cap) 11 | end 12 | last_end = e+1 13 | s, e, cap = str:find(fpat, last_end) 14 | end 15 | if last_end <= #str then 16 | cap = str:sub(last_end) 17 | table.insert(t, cap) 18 | end 19 | return t 20 | end 21 | 22 | function _M.incr_or_create(stats, key, count) 23 | local newval, err = stats:incr(key, count) 24 | if not newval and err == "not found" then 25 | stats:add(key, 0) 26 | stats:incr(key, count) 27 | end 28 | end 29 | 30 | 31 | function _M.key(key_table) 32 | return table.concat(key_table, ':') 33 | end 34 | 35 | 36 | function _M.in_table (value, table) 37 | for _, v in pairs(table) do 38 | if (v == value) then 39 | return true 40 | end 41 | end 42 | return false 43 | end 44 | 45 | 46 | function _M.format_response(key, value, response) 47 | local path = split(tostring(key), ':') 48 | key = table.remove(path, 1) 49 | if key == nil or key == '' then 50 | return value 51 | elseif response[key] == nil then 52 | response[key] = _M.format_response(_M.key(path), value, {}) 53 | elseif response[key] ~= nil then 54 | response[key] = _M.format_response(_M.key(path), value, response[key]) 55 | end 56 | return response 57 | end 58 | 59 | 60 | function _M.get_status_code_class(status) 61 | if status:sub(1,1) == '1' then 62 | return "1xx" 63 | elseif status:sub(1,1) == '2' then 64 | return "2xx" 65 | elseif status:sub(1,1) == '3' then 66 | return "3xx" 67 | elseif status:sub(1,1) == '4' then 68 | return "4xx" 69 | elseif status:sub(1,1) == '5' then 70 | return "5xx" 71 | else 72 | return "xxx" 73 | end 74 | end 75 | 76 | 77 | function _M.update(stats, key, value) 78 | stats:set(key, value) 79 | end 80 | 81 | 82 | return _M 83 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -- http://www.kyne.com.au/~mark/software/lua-cjson.php 2 | cjson = require "cjson" 3 | common = require "stats.common" 4 | cache_status = {"MISS", "BYPASS", "EXPIRED", "STALE", "UPDATING", "REVALIDATED", "HIT"} 5 | local stats = ngx.shared.ngx_stats 6 | ngx.update_time() 7 | 8 | -- Geral stats 9 | stats:set('stats_start', ngx.now()) 10 | -------------------------------------------------------------------------------- /log.lua: -------------------------------------------------------------------------------- 1 | ngx.update_time() 2 | local stats = ngx.shared.ngx_stats; 3 | local group = ngx.var.stats_group 4 | local req_time = (tonumber(ngx.now() - ngx.req.start_time()) * 1000) 5 | local status = tostring(ngx.status) 6 | 7 | -- Geral stats 8 | local upstream_response_time = tonumber(ngx.var.upstream_response_time) 9 | 10 | -- Set default group, if it's not defined by nginx variable 11 | if not group or group == "" then 12 | group = 'other' 13 | end 14 | 15 | 16 | common.incr_or_create(stats, common.key({group, 'requests_total'}), 1) 17 | 18 | if req_time >= 0 and req_time < 100 then 19 | common.incr_or_create(stats, common.key({group, 'request_times', '0-100'}), 1) 20 | elseif req_time >= 100 and req_time < 500 then 21 | common.incr_or_create(stats, common.key({group, 'request_times', '100-500'}), 1) 22 | elseif req_time >= 500 and req_time < 1000 then 23 | common.incr_or_create(stats, common.key({group, 'request_times', '500-1000'}), 1) 24 | elseif req_time >= 1000 then 25 | common.incr_or_create(stats, common.key({group, 'request_times', '1000-inf'}), 1) 26 | end 27 | 28 | if upstream_response_time then 29 | common.incr_or_create(stats, common.key({group, 'upstream_requests_total'}), 1) 30 | common.incr_or_create(stats, common.key({group, 'upstream_resp_time_sum'}), (upstream_response_time or 0)) 31 | end 32 | 33 | 34 | if common.in_table(ngx.var.upstream_cache_status, cache_status) then 35 | local status = string.lower(ngx.var.upstream_cache_status) 36 | common.incr_or_create(stats, common.key({group, 'cache', status}), 1) 37 | end 38 | 39 | common.incr_or_create(stats, common.key({group, 'status', common.get_status_code_class(status)}), 1) 40 | 41 | -- Traffic being sent to and from the client 42 | common.update(stats, common.key({group, 'traffic', 'received'}), ngx.var.request_length) 43 | common.update(stats, common.key({group, 'traffic', 'sent'}), ngx.var.bytes_sent) 44 | 45 | 46 | --[[ Connection statistics 47 | 48 | Active connections 49 | ================== 50 | The current number of active client connections including Waiting connections. 51 | 52 | Reading connections 53 | =================== 54 | The current number of connections where nginx is reading the request header. 55 | 56 | Waiting connections 57 | =================== 58 | The current number of idle client connections waiting for a request. 59 | 60 | Writing connections 61 | =================== 62 | The current number of connections where nginx is writing the response back to the client. 63 | 64 | ]]-- 65 | common.update(stats, common.key({'connections', 'active'}), ngx.var.connections_active) 66 | common.update(stats, common.key({'connections', 'idle'}), ngx.var.connections_waiting) 67 | common.update(stats, common.key({'connections', 'reading'}), ngx.var.connections_reading) 68 | common.update(stats, common.key({'connections', 'writing'}), ngx.var.connections_writing) 69 | 70 | common.update(stats, common.key({'requests', 'current'}), ngx.var.connection_requests) 71 | common.incr_or_create(stats, common.key({'requests', 'total'}), 1) 72 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | user www-data; 2 | worker_processes 1; 3 | error_log /var/log/nginx/error.log error; 4 | pid /var/run/nginx.pid; 5 | events { 6 | worker_connections 61440; 7 | } 8 | 9 | http { 10 | access_log off; 11 | 12 | lua_shared_dict ngx_stats 10m; 13 | lua_package_path '/etc/nginx/lua/?.lua;;'; 14 | lua_package_cpath '/etc/nginx/lua/?.so;;'; 15 | init_by_lua_file /etc/nginx/lua/stats/init.lua; 16 | 17 | proxy_cache_path /var/tmp/cache_http levels=1:1:1 18 | keys_zone=g_cache:64m 19 | max_size=1024m inactive=4h; 20 | 21 | server { 22 | listen 80; 23 | server_name example.com; 24 | set $stats_group "example.com"; 25 | log_by_lua_file /etc/nginx/lua/stats/log.lua; 26 | location / { 27 | proxy_pass "http://127.0.0.1:8080"; 28 | proxy_cache g_cache; 29 | } 30 | location = /status.json { 31 | default_type 'application/json'; 32 | content_by_lua_file /etc/nginx/lua/stats/show.lua; 33 | } 34 | } 35 | server { 36 | listen 80; 37 | server_name sub.example.com; 38 | set $stats_group "sub.example.com"; 39 | log_by_lua_file /etc/nginx/lua/stats/log.lua; 40 | location / { 41 | proxy_pass "http://127.0.0.1:8081"; 42 | proxy_cache g_cache; 43 | } 44 | } 45 | server { 46 | listen 80 default_server; 47 | server_name _; 48 | log_by_lua_file /etc/nginx/lua/stats/log.lua; 49 | return 404; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /show.lua: -------------------------------------------------------------------------------- 1 | local stats = ngx.shared.ngx_stats; 2 | local keys = stats:get_keys() 3 | local response = {} 4 | 5 | for k,v in pairs(keys) do 6 | response = common.format_response(v, stats:get(v), response) 7 | end 8 | 9 | ngx.say(cjson.encode(response)) 10 | --------------------------------------------------------------------------------