Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
{"schema":"libjg2-1", "vpath":"/git/", "avatar":"/git/avatar/", "alang":"", "gen_ut":1756926897, "reponame":"libwebsockets", "desc":"libwebsockets lightweight C networking library", "owner": { "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" },"url":"https://libwebsockets.org/repo/libwebsockets", "f":3, "items": [ {"schema":"libjg2-1", "cid":"c5fd7d05044d0972e18292bf3a115570", "commit": {"type":"commit", "time": 1615240048, "time_ofs": 0, "oid_tree": { "oid": "f1a29f8e398094c0110fc90c08f7f68935508e00", "alias": []}, "oid":{ "oid": "3f4623bb36b6079b3dc59560a9b3ffa1043e3474", "alias": [ "refs/tags/v4.2-rc1"]}, "msg": "lws_metrics", "sig_commit": { "git_time": { "time": 1615240048, "offset": 0 }, "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" }, "sig_author": { "git_time": { "time": 1609945702, "offset": 0 }, "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" }}, "body": "lws_metrics\n\nThere are a few build options that are trying to keep and report\nvarious statistics\n\n - DETAILED_LATENCY\n - SERVER_STATUS\n - WITH_STATS\n\nremove all those and establish a generic rplacement, lws_metrics.\n\nlws_metrics makes its stats available via an lws_system ops function\npointer that the user code can set.\n\nOpenmetrics export is supported, for, eg, prometheus scraping.\n" , "diff": "diff --git a/.sai.json b/.sai.json\nindex 7b08cf7..26a2de8 100644\n--- a/.sai.json\n+++ b/.sai.json\n@@ -54,7 +54,7 @@\n \t\t\t\u0022build\u0022: \u0022mkdir build destdir; cd build; export LD_LIBRARY_PATH\u003d../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib;export SAI_CPACK\u003d\u005c\u0022-G ZIP\u005c\u0022;export MACOSX_DEPLOYMENT_TARGET\u003d10.15 ; cmake .. -DCMAKE_MAKE_PROGRAM\u003d/usr/bin/make -DLWS_OPENSSL_INCLUDE_DIRS\u003d/usr/local/opt/openssl@1.1/include -DLWS_OPENSSL_LIBRARIES\u003d\u005c\u0022/usr/local/opt/openssl/lib/libssl.dylib;/usr/local/opt/openssl/lib/libcrypto.dylib\u005c\u0022 ${cmake} \u0026\u0026 make -j4 \u0026\u0026 make -j DESTDIR\u003d../destdir install \u0026\u0026 ctest -j2 --output-on-failure ${cpack}\u0022\n \t\t},\n \t\t\u0022netbsd-OSX-bigsur/aarch64-apple-m1/llvm\u0022: {\n-\t\t\t\u0022build\u0022: \u0022mkdir build destdir; cd build; export LD_LIBRARY_PATH\u003d../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib;export SAI_CPACK\u003d\u005c\u0022-G ZIP\u005c\u0022;export MACOSX_DEPLOYMENT_TARGET\u003d10.15 ; cmake .. -DLWS_WITH_SUL_DEBUGGING\u003d1 -DCMAKE_SYSTEM_PREFIX_PATH\u003d/opt/homebrew -DLWS_OPENSSL_INCLUDE_DIRS\u003d/opt/homebrew/Cellar/openssl@1.1/1.1.1h/include '-DLWS_OPENSSL_LIBRARIES\u003d/opt/homebrew/Cellar/openssl@1.1/1.1.1h/lib/libssl.dylib;/opt/homebrew/Cellar/openssl@1.1/1.1.1h/lib/libcrypto.dylib' -DLWS_WITH_MINIMAL_EXAMPLES\u003d1 ${cmake} \u0026\u0026 make -j6 \u0026\u0026 rm -rf ../destdir \u0026\u0026 make -j DESTDIR\u003d../destdir install \u0026\u0026 ctest -j3 --output-on-failure ${cpack}\u0022\n+\t\t\t\u0022build\u0022: \u0022mkdir build destdir; cd build; export LD_LIBRARY_PATH\u003d../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib;export SAI_CPACK\u003d\u005c\u0022-G ZIP\u005c\u0022;export MACOSX_DEPLOYMENT_TARGET\u003d10.15 ; cmake .. -DLWS_WITH_SUL_DEBUGGING\u003d1 -DCMAKE_SYSTEM_PREFIX_PATH\u003d/opt/homebrew -DLWS_OPENSSL_INCLUDE_DIRS\u003d/opt/homebrew/Cellar/openssl@1.1/1.1.1h/include '-DLWS_OPENSSL_LIBRARIES\u003d/opt/homebrew/Cellar/openssl@1.1/1.1.1h/lib/libssl.dylib;/opt/homebrew/Cellar/openssl@1.1/1.1.1h/lib/libcrypto.dylib' ${cmake} \u0026\u0026 make -j6 \u0026\u0026 rm -rf ../destdir \u0026\u0026 make -j DESTDIR\u003d../destdir install \u0026\u0026 ctest -j3 --output-on-failure ${cpack}\u0022\n \t\t},\n \t\t\u0022solaris/x86_64-amd/gcc\u0022: {\n \t\t\t\u0022build\u0022: \u0022mkdir build destdir; cd build; export SAI_CPACK\u003d\u005c\u0022-G ZIP\u005c\u0022;cmake .. ${cmake} \u0026\u0026 make -j 4 \u0026\u0026 make install DESTDIR\u003d../destdir \u0026\u0026 ctest -j2 --output-on-failure ${cpack}\u0022,\n@@ -115,7 +115,7 @@\n \t\t},\n \t\t\u0022default-noudp\u0022: {\n \t\t\t\u0022cmake\u0022:\t\u0022-DLWS_WITH_UDP\u003d0\u0022,\n-\t\t\t\u0022platforms\u0022:\t\u0022w10/x86_64-amd/msvc, w10/x86_64-amd/noptmsvc, freertos-linkit/arm32-m4-mt7697-usi/gcc, linux-ubuntu-2004/aarch64-a72-bcm2711-rpi4/gcc, w10/x86_64-amd/mingw32, w10/x86_64-amd/mingw64, netbsd/aarch64BE-bcm2837-a53/gcc, w10/x86_64-amd/wmbedtls-msvc\u0022\n+\t\t\t\u0022platforms\u0022:\t\u0022w10/x86_64-amd/msvc, w10/x86_64-amd/noptmsvc, freertos-linkit/arm32-m4-mt7697-usi/gcc, linux-ubuntu-2004/aarch64-a72-bcm2711-rpi4/gcc, w10/x86_64-amd/mingw32, w10/x86_64-amd/mingw64, netbsd/aarch64BE-bcm2837-a53/gcc, w10/x86_64-amd/wmbedtlsmsvc\u0022\n \t\t},\n \t\t\u0022esp32-heltec\u0022: {\n \t\t\t\u0022cmake\u0022:\t\u0022\u0022,\n@@ -181,7 +181,10 @@\n \t\t\t\u0022cmake\u0022:\t\u0022-DLWS_WITH_SECURE_STREAMS\u003d1 -DLWS_WITH_SECURE_STREAMS_PROXY_API\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1 -DLWS_WITH_SECURE_STREAMS_AUTH_SIGV4\u003d1\u0022,\n \t\t\t\u0022platforms\u0022:\t\u0022not w10/x86_64-amd/msvc, netbsd/aarch64BE-bcm2837-a53/gcc, openbsd/x86_64-amd/llvm, solaris/x86_64-amd/gcc\u0022\n \t\t},\n-\n+\t\t\u0022secure-streams-proxy-metrics\u0022: {\n+\t\t\t\u0022cmake\u0022:\t\u0022-DLWS_WITH_SECURE_STREAMS\u003d1 -DLWS_WITH_SECURE_STREAMS_PROXY_API\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1 -DLWS_WITH_SECURE_STREAMS_AUTH_SIGV4\u003d1 -DLWS_WITH_SYS_METRICS\u003d1\u0022,\n+\t\t\t\u0022platforms\u0022:\t\u0022not w10/x86_64-amd/msvc, netbsd/aarch64BE-bcm2837-a53/gcc\u0022\n+\t\t},\n \t\t\u0022distro_recommended\u0022: { # minimal examples also needed for ctest\n \t\t\t\u0022cmake\u0022:\t\u0022-DLWS_WITH_DISTRO_RECOMMENDED\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1\u0022,\n \t\t\t\u0022platforms\u0022:\t\u0022not freebsd-12/x86_64-amd/llvm, not linkit-cross, not w10/x86_64-amd/msvc, linux-ubuntu-2004/aarch64-a72-bcm2711-rpi4/gcc, linux-fedora-32/riscv64-virt/gcc\u0022,\n@@ -193,6 +196,11 @@\n \t\t\t# no distro -devel package for libuv\n \t\t\t\u0022platforms\u0022:\t\u0022not linux-centos-8/x86_64-amd/gcc\u0022\n \t\t},\n+\t\t\u0022lwsws-nometrics\u0022: {\n+\t\t\t\u0022cmake\u0022:\t\u0022-DLWS_WITH_LWSWS\u003dON -DLWS_WITHOUT_EXTENSIONS\u003d0 -DLWS_WITH_HTTP2\u003d1 -DLWS_WITH_ACME\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1 -DCMAKE_BUILD_TYPE\u003dDEBUG -DLWS_WITH_GENCRYPTO\u003d1 -DLWS_WITH_JOSE\u003d1 -DLWS_WITH_SYS_ASYNC_DNS\u003d1 -DLWS_WITH_SYS_NTPCLIENT\u003d1 -DLWS_WITH_SYS_METRICS\u003d0\u0022,\n+\t\t\t# no distro -devel package for libuv\n+\t\t\t\u0022platforms\u0022:\t\u0022not linux-centos-8/x86_64-amd/gcc\u0022\n+\t\t},\n \t\t\u0022lwsws2\u0022: {\n \t\t\t\u0022cmake\u0022:\t\u0022-DLWS_WITH_LWSWS\u003dON -DLWS_WITHOUT_EXTENSIONS\u003d0 -DLWS_WITH_HTTP2\u003d1 -DLWS_WITH_ACME\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1 -DCMAKE_BUILD_TYPE\u003dDEBUG -DLWS_WITH_LWS_DSH\u003d1\u0022,\n \t\t\t# no distro -devel package for libuv\n@@ -207,6 +215,10 @@\n \t\t\t# no distro -devel package for mbedtls\n \t\t\t\u0022platforms\u0022:\t\u0022not linux-centos-7/x86_64-amd/gcc, not linux-centos-8/x86_64-amd/gcc\u0022\n \t\t},\n+\t\t\u0022mbedtls-metrics\u0022: {\n+\t\t\t\u0022cmake\u0022:\t\u0022-DLWS_WITH_MBEDTLS\u003d1 -DLWS_WITH_HTTP2\u003d1 -DLWS_WITH_LWSWS\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1 -DLWS_WITH_JOSE\u003d1 -DCMAKE_BUILD_TYPE\u003dDEBUG -DLWS_WITH_SYS_METRICS\u003d1\u0022,\n+\t\t\t\u0022platforms\u0022:\t\u0022not linux-centos-7/x86_64-amd/gcc, not linux-centos-8/x86_64-amd/gcc\u0022\n+\t\t},\n \t\t\u0022noserver\u0022: {\n \t\t\t\u0022cmake\u0022:\t\u0022-DLWS_WITHOUT_SERVER\u003dON -DLWS_WITH_MINIMAL_EXAMPLES\u003d1 -DLWS_WITH_SECURE_STREAMS\u003d1\u0022,\n \t\t\t\u0022platforms\u0022: \u0022w10/x86_64-amd/msvc, w10/x86_64-amd/noptmsvc\u0022\ndiff --git a/CMakeLists-implied-options.txt b/CMakeLists-implied-options.txt\nindex 6c98359..89e319e 100644\n--- a/CMakeLists-implied-options.txt\n+++ b/CMakeLists-implied-options.txt\n@@ -83,7 +83,7 @@ if(LWS_WITH_DISTRO_RECOMMENDED)\n \tset(LWS_WITH_SOCKS5 1)\t\t\t\t# selfcontained\n \tset(LWS_WITH_RANGES 1)\t\t\t\t# selfcontained\n \tset(LWS_WITH_ACME 1)\t\t\t\t# selfcontained / tls\n-\tset(LWS_WITH_SERVER_STATUS 1)\t\t\t# selfcontained\n+\tset(LWS_WITH_SYS_METRICS 1)\t\t\t# selfcontained\n \tset(LWS_WITH_GLIB 1)\t\t\t\t# glib\n \tset(LWS_WITH_LIBUV 1)\t\t\t\t# libuv\n \tset(LWS_WITH_LIBEV 1)\t\t\t\t# libev\n@@ -128,6 +128,7 @@ endif()\n if (LWS_WITH_SECURE_STREAMS_PROXY_API)\n \tset(LWS_WITH_LWS_DSH 1)\n \tset(LWS_WITH_UNIX_SOCK 1)\n+\tset(LWS_WITH_SYS_SMD 1)\n endif()\n \n if (NOT LWS_WITH_NETWORK)\n@@ -210,7 +211,7 @@ if (LWS_WITH_LWSWS)\n set(LWS_WITH_LIBUV_INTERNAL 1)\n set(LWS_WITH_EVENT_LIBS 1) # implied by LIBUV_INTERNAL\n set(LWS_WITH_ACCESS_LOG 1)\n- set(LWS_WITH_SERVER_STATUS 1)\n+ set(LWS_WITH_SYS_METRICS 1)\n set(LWS_WITH_LEJP 1)\n set(LWS_WITH_LEJP_CONF 1)\n set(LWS_WITH_PEER_LIMITS 1)\ndiff --git a/CMakeLists.txt b/CMakeLists.txt\nindex 9be767f..996a545 100644\n--- a/CMakeLists.txt\n+++ b/CMakeLists.txt\n@@ -112,7 +112,6 @@ option(LWS_WITH_SOCKS5 \u0022Allow use of SOCKS5 proxy on client connections\u0022 OFF)\n option(LWS_WITH_PEER_LIMITS \u0022Track peers and restrict resources a single peer can allocate\u0022 OFF)\n option(LWS_WITH_ACCESS_LOG \u0022Support generating Apache-compatible access logs\u0022 OFF)\n option(LWS_WITH_RANGES \u0022Support http ranges (RFC7233)\u0022 OFF)\n-option(LWS_WITH_SERVER_STATUS \u0022Support json + jscript server monitoring\u0022 OFF)\n option(LWS_WITH_THREADPOOL \u0022Managed worker thread pool support (relies on pthreads)\u0022 OFF)\n option(LWS_WITH_HTTP_STREAM_COMPRESSION \u0022Support HTTP stream compression\u0022 OFF)\n option(LWS_WITH_HTTP_BROTLI \u0022Also offer brotli http stream compression (requires LWS_WITH_HTTP_STREAM_COMPRESSION)\u0022 OFF)\n@@ -134,6 +133,7 @@ else()\n \toption(LWS_WITH_RFC6724 \u0022Enable RFC6724 DNS result sorting\u0022 OFF)\n endif()\n option(LWS_WITH_SYS_FAULT_INJECTION \u0022Enable fault injection support\u0022 OFF)\n+option(LWS_WITH_SYS_METRICS \u0022Lws Metrics API\u0022 OFF)\n \n #\n # Secure Streams\n@@ -250,7 +250,6 @@ set(LWS_LOGGING_BITFIELD_CLEAR 0 CACHE STRING \u0022Bitfield describing which log lev\n option(LWS_LOGS_TIMESTAMP \u0022Timestamp at start of logs\u0022 ON)\n option(LWS_LOG_TAG_LIFECYCLE \u0022Log tagged object lifecycle as NOTICE\u0022 ON)\n option(LWS_AVOID_SIGPIPE_IGN \u0022Android 7+ reportedly needs this\u0022 OFF)\n-option(LWS_WITH_STATS \u0022Keep statistics of lws internal operations\u0022 OFF)\n option(LWS_WITH_JOSE \u0022JSON Web Signature / Encryption / Keys (RFC7515/6/) API\u0022 OFF)\n option(LWS_WITH_GENCRYPTO \u0022Enable support for Generic Crypto apis independent of TLS backend\u0022 OFF)\n option(LWS_WITH_SELFTESTS \u0022Selftests run at context creation\u0022 OFF)\n@@ -272,7 +271,6 @@ option(LWS_WITH_EXTERNAL_POLL \u0022Support external POLL integration using callback \n option(LWS_WITH_LWS_DSH \u0022Support lws_dsh_t Disordered Shared Heap\u0022 OFF)\n option(LWS_CLIENT_HTTP_PROXYING \u0022Support external http proxies for client connections\u0022 ON)\n option(LWS_WITH_FILE_OPS \u0022Support file operations vfs\u0022 ON)\n-option(LWS_WITH_DETAILED_LATENCY \u0022Record detailed latency stats for each read and write\u0022 OFF)\n option(LWS_WITH_UDP \u0022Platform supports UDP\u0022 ON)\n option(LWS_WITH_SPAWN \u0022Spawn subprocesses with piped stdin/out/stderr\u0022 OFF)\n option(LWS_WITH_FSMOUNT \u0022Overlayfs and fallback mounting apis\u0022 OFF)\ndiff --git a/README.md b/README.md\nindex 3aa3c97..2013f43 100644\n--- a/README.md\n+++ b/README.md\n@@ -378,7 +378,8 @@ with `api-tests/api-test-async-dns` minimal example.\n You can now opt to measure and store us-resolution statistics on effective\n latencies for client operations, and easily spool them to a file in a\n format suitable for gnuplot, or handle in your own callback. Enable\n-`-DLWS_WITH_DETAILED_LATENCY\u003d1` in cmake to build it into lws.\n+`-DLWS_WITH_DETAILED_LATENCY\u003d1` in cmake to build it into lws. (NB 2021-01-12\n+this has been replaced by the lws_metrics support)\n \n If you are concerned about operation latency or potential blocking from\n user code, or behaviour under load, or latency variability on specific\ndiff --git a/READMEs/README.detailed-latency.md b/READMEs/README.detailed-latency.md\ndeleted file mode 100644\nindex 29e29d0..0000000\n--- a/READMEs/README.detailed-latency.md\n+++ /dev/null\n@@ -1,117 +0,0 @@\n-# lws detailed latency\n-\n-![lws detailed latency example plot](../doc-assets/lws-detailed-latency-example.png)\n-\n-## Introduction\n-\n-lws has the capability to make detailed latency measurements and\n-report them in realtime to a specified callback.\n-\n-A default callback is provided that renders the data as text in\n-space-separated format suitable for gnuplot, to a specified file.\n-\n-## Configuring\n-\n-Enable `LWS_WITH_DETAILED_LATENCY` at cmake.\n-\n-Create your context with something similar to this\n-\n-```\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n-\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-results\u0022;\n-#endif\n-```\n-\n-`lws_det_lat_plot_cb` is provided by lws as a convenience to convert\n-the stuct data provided at the callback interface to space-separated\n-text data that is easy to process with shell commands and gnuplot.\n-\n-## `lws_det_lat_plot_cb` format\n-\n-```\n-728239173547 N 23062 0 0 23062 0 0 0\n-728239192554 C 18879 0 0 18879 0 0 0\n-728239217894 T 25309 0 0 25309 0 0 0\n-728239234998 r 0 0 0 0 271 172 256\n-728239250611 r 0 0 0 0 69 934 4096\n-728239255679 w 19 122 18 159 20 80 80\n-728239275718 w 20 117 15 152 18 80 80\n-728239295578 w 10 73 7 90 7 80 80\n-728239315567 w 9 67 5 81 7 80 80\n-728239335745 w 23 133 9 165 14 80 80\n-...\n-```\n-\n-Each event is shown in 9 columns\n-\n- - unix time in us\n- - event type\n- - N \u003d Name resolution\n- - C \u003d TCP Connection\n- - T \u003d TLS negotiation server\n- - t \u003d TLS negotiation client\n- - r \u003d Read\n- - w \u003d Write\n- - us duration, for w time client spent waiting to write\n- - us duration, for w time data spent in transit to proxy\n- - us duration, for w time proxy waited to send data\n- - as a convenience, sum of last 3 columns above\n- - us duration, time spent in callback\n- - last 2 are actual / requested size in bytes\n-\n-## Processing captured data with ministat\n-\n-Eg, to summarize overall latencies on all captured writes\n-\n-```\n- $ cat /tmp/lws-latency-results | grep \u0022 w \u0022 | cut -d' ' -f6 | ministat\n-...\n- N Min Max Median Avg Stddev\n-x 1000 43 273 141 132.672 32.471693\n-```\n-\n-## Processing captured data with gnuplot\n-\n-### Gnuplot plotting script\n-\n-Create a gnuplot script, eg myscript.gp\n-\n-```\n-reset\n-set term pngcairo enhanced nocrop font \u0022OpenSans, 12\u0022 size 800,600#output terminal and file\n-set output \u0022lws-latency.png\u0022\n-#set yrange [0:10000]\n-#to put an empty boundary around the\n-#data inside an autoscaled graph.\n-set offset graph 0.05,0.05,0.05,0.0\n-set style fill transparent solid 0.5 #fillstyle\n-set tics out nomirror\n-set xlabel \u0022event\u0022\n-set ylabel \u0022latency (us)\u0022\n-set format x \u0022\u0022\n-set title \u0022Write latency\u0022\n-set key invert reverse Right inside nobox\n-set key autotitle columnheader\n-set style data histogram\n-set style histogram rowstacked\n-set style fill solid border -1\n-set boxwidth 0.75\n-set style fill solid 1.00 noborder\n-set tic scale 0\n-set grid ytics lc rgb \u0022#505050\u0022\n-unset border\n-unset xtics\n-\n-plot '/tmp/1' \u005c\n-\t using ($3 + $4 + $5):xtic(1) w boxes lt rgbcolor \u0022blue\u0022 title 'prox wr wait', \u005c\n-\t'' using ($3 + $4):xtic(1) w boxes lt rgbcolor \u0022green\u0022 title 'txfr to prox', \u005c\n-\t'' using 3:xtic(1) w boxes lt rgbcolor \u0022red\u0022 title 'cli wri wait'\n-```\n-\n-### gnuplot invocation\n-\n-```\n- $ cat /tmp/lws-latency-results | grep \u0022 w \u0022 \u005c\u003e/tmp/1 ; gnuplot myscript.gp \u0026\u0026 eog lws-latency.png\n-```\n-\ndiff --git a/READMEs/README.lws_metrics.md b/READMEs/README.lws_metrics.md\nnew file mode 100644\nindex 0000000..82cd2a5\n--- /dev/null\n+++ b/READMEs/README.lws_metrics.md\n@@ -0,0 +1,245 @@\n+## `lws_metrics`\n+\n+### Introduction\n+\n+`lws_metrics` records and aggregates **events** at all lws layers.\n+\n+There are three distinct parts:\n+\n+ - the architecture inside lws for collecting and aggregating / decimating the\n+ events and maintaining statistics about them, these are lws_metric objects\n+\n+ - an external handler for forwarding aggregated metrics. An lws_system ops\n+ interface to pass on the aggregated metrics to an external backend. lws\n+ presents its own public metrics objects and leaves it to the external\n+ code to have a shim to marry the lws metrics up to whatever is needed in the\n+ metrics backend\n+\n+ - a policy for when to emit each type of aggregated information to the external\n+ handler. This can be specified in the generic Secure Streams policy, or\n+ a linked-list of lws_metric_policy_t object passed it at context creation in\n+ `info.metrics_policies`.\n+\n+The external backend interface code may itself make use of lws connectivity apis\n+including Secure Streams itself, and lws metrics are available on that too.\n+\n+### `lws_metrics` policy-based reporting\n+\n+Normally metrics implementations are fixed at build-time and cannot change\n+without a coordinated reflash of devices along with a change of backend schema.\n+\n+`lws_metrics` separates out the objects and code necessary to collect and\n+aggregate the data cheaply, and the reporting policy that controls if, or how\n+often, the results are reported to the external handler.\n+\n+![policy based metrics](/doc-assets/lws_metrics-policy.png)\n+\n+Metrics are created with a namespace name and the policy applies itself to those\n+by listing the names, with wildcards allowed, the policy applies to, eg if\n+specified in the Secure Streams JSON policy\n+\n+```\n+\t...\n+\t\u0022metrics\u0022: [\n+ {\n+ \u0022name\u0022: \u0022tensecs\u0022,\n+ \u0022us_schedule\u0022: 10000000,\n+ \u0022report\u0022:\t\u0022cpu.*\u0022\n+ }, {\n+ \u0022name\u0022: \u002230secs\u0022,\n+ \u0022us_schedule\u0022: 30000000,\n+ \u0022report\u0022: \u0022n.cn.*, n.http.*, n.ss.*, vh.*\u0022\n+ }\n+ ],\n+ ...\n+```\n+\n+Metrics that do not have a reporting policy do not report, but continue to\n+aggregate measurements in case they are bound to a policy dynamically later.\n+\n+### Freeform metrics naming\n+\n+There is no predefined metrics schema, metrics objects, including those created\n+by applications, can independently choose their own name in a namespace like\n+\u0022cpu.srv\u0022 or \u0022n.cn.dns\u0022, and can set a prefix for all metrics names created in a\n+context (by setting `info.metrics_prefix` at context creation time).\n+\n+This allows multiple processes in a single device to expose copies of the same\n+metrics in an individually addressable way, eg, if the UI process specifies the\n+prefix \u0022ui\u0022, then its lws metrics like \u0022cpu.srv\u0022 will actually be created as\n+\u0022ui.cpu.srv\u0022.\n+\n+Applications can freely define their own `lws_metrics` measurements with their\n+own names in the namespace too, without central registration, and refer to those\n+names in the reporting policy same as any other metric names.\n+\n+If the metrics backend requires a fixed schema, the mapping between the\n+`lws_metrics` names and the backend schema indexes will be done in the\n+`lws_system` external reporting api implementation alone. Metrics objects\n+contain a `void * backend_opaque` that is ignored by lws and can be set and\n+read by the external reporting handler implementation to facilitate that.\n+\n+### Histogram metrics tagging\n+\n+Histogram metrics track differently-qualified results in the same metric, for\n+example the metric `n.cn.failures` maintains separate result counts for all\n+variations and kinds of failure.\n+\n+```\n+[2021/03/01 06:34:05:6570] U: my_metric_report: ssproxy.n.cn.failures{ss\u003d\u0022badcert_selfsigned\u0022,hostname\u003d\u0022invalidca.badcert.warmcat.com\u0022,peer\u003d\u002246.105.127.147\u0022,tls\u003d\u0022invalidca\u0022} 2\n+[2021/03/01 06:34:05:6573] U: my_metric_report: ssproxy.n.cn.failures{hostname\u003d\u0022invalidca.badcert.warmcat.com\u0022,peer\u003d\u002246.105.127.147\u0022,tls\u003d\u0022invalidca\u0022} 1\n+[2021/03/01 06:34:05:6576] U: my_metric_report: ssproxy.n.cn.failures{ss\u003d\u0022badcert_expired\u0022,hostname\u003d\u0022warmcat.com\u0022,peer\u003d\u002246.105.127.147\u0022,tls\u003d\u0022expired\u0022} 2\n+[2021/03/01 06:34:05:6578] U: my_metric_report: ssproxy.n.cn.failures{hostname\u003d\u0022warmcat.com\u0022,peer\u003d\u002246.105.127.147\u0022,tls\u003d\u0022expired\u0022} 1\n+[2021/03/01 06:34:05:6580] U: my_metric_report: ssproxy.n.cn.failures{ss\u003d\u0022badcert_hostname\u0022,hostname\u003d\u0022hostname.badcert.warmcat.com\u0022,peer\u003d\u002246.105.127.147\u0022,tls\u003d\u0022hostname\u0022} 2\n+[2021/03/01 06:34:05:6583] U: my_metric_report: ssproxy.n.cn.failures{hostname\u003d\u0022hostname.badcert.warmcat.com\u0022,peer\u003d\u002246.105.127.147\u0022,tls\u003d\u0022hostname\u0022} 1\n+[2021/03/01 06:34:05:6585] U: my_metric_report: ssproxy.n.cn.failures{dns\u003d\u0022nores -2\u0022} 8\n+```\n+\n+The user handler for metrics is expected to iterate these, in the provided\n+examples (eg, minimal-secure-streams-testsfail)\n+\n+```\n+#if defined(LWS_WITH_SYS_METRICS)\n+static int\n+my_metric_report(lws_metric_pub_t *mp)\n+{\n+\tlws_metric_bucket_t *sub \u003d mp-\u003eu.hist.head;\n+\tchar buf[192];\n+\n+\tdo {\n+\t\tif (lws_metrics_format(mp, \u0026sub, buf, sizeof(buf)))\n+\t\t\tlwsl_user(\u0022%s: %s\u005cn\u0022, __func__, buf);\n+\t} while ((mp-\u003eflags \u0026 LWSMTFL_REPORT_HIST) \u0026\u0026 sub);\n+\n+\t/* 0 \u003d leave metric to accumulate, 1 \u003d reset the metric */\n+\n+\treturn 1;\n+}\n+\n+static const lws_system_ops_t system_ops \u003d {\n+\t.metric_report \u003d my_metric_report,\n+};\n+\n+#endif\n+```\n+\n+### `lws_metrics` decimation\n+\n+Event information can easily be produced faster than it can be transmitted, or\n+is useful to record if everything is working. In the case that things are not\n+working, then eventually the number of events that are unable to be forwarded\n+to the backend would overwhelm the local storage.\n+\n+For that reason, the metrics objects are designed to absorb and summarize a\n+potentially large number of events cheaply by aggregating them, so even extreme\n+situations can be tracked meaningfully inbetween dumps to the backend.\n+\n+There are two approaches:\n+\n+ - \u0022aggregation\u0022: decimate keeping a uint64 mean + sum, along with a max and min\n+ \n+ - \u0022histogram\u0022: keep a linked-list of different named buckets, with a 64-bit\n+ counter for the number of times an event in each bucket was observed\n+\n+A single metric aggregation object has separate \u0022go / no-go\u0022 counters, since\n+most operations can fail, and failing operations act differently.\n+\n+`lws_metrics` 'aggregation' supports decimation by\n+\n+ - a mean of a 64-bit event metric, separate for go and no-go events\n+ - counters of go and no-go events\n+ - a min and max of the metric\n+ - keeping track of when the sample period started\n+\n+![metrics decimation](/doc-assets/lws_metrics-decimation.png)\n+\n+In addition, the policy defines a percentage variance from the mean that\n+optionally qualifies events to be reported individually.\n+\n+The `lws_metrics` 'histogram' allows monitoring of different outcomes to\n+produce counts of each outcome in the \u0022bucket\u0022. \n+\n+### `lws_metrics` flags\n+\n+When the metrics object is created, flags are used to control how it will be\n+used and consumed.\n+\n+For example to create a histogram metrics object rather than the default\n+aggregation type, you would give the flag `LWSMTFL_REPORT_HIST` at creation\n+time.\n+\n+|Flag|Meaning|\n+|---|---|\n+|`LWSMTFL_REPORT_OUTLIERS`|track outliers and report them internally|\n+|`LWSMTFL_REPORT_OUTLIERS_OOB`|report each outlier externally as they happen|\n+|`LWSMTFL_REPORT_INACTIVITY_AT_PERIODIC`|explicitly externally report no activity at periodic cb, by default no events in the period is just not reported|\n+|`LWSMTFL_REPORT_MEAN`|the mean is interesting for this metric|\n+|`LWSMTFL_REPORT_ONLY_GO`|no-go pieces invalid and should be ignored, used for simple counters|\n+|`LWSMTFL_REPORT_DUTY_WALLCLOCK_US`|the aggregated sum or mean can be compared to wallclock time| \n+|`LWSMTFL_REPORT_HIST`|object is a histogram (else aggregator)|\n+\n+### Built-in lws-layer metrics\n+\n+lws creates and maintains various well-known metrics when you enable build\n+with cmake `-DLWS_WITH_SYS_METRICS\u003d1`:\n+\n+#### Aggregation metrics\n+|metric name|scope|type|meaning|\n+---|---|---|---|\n+`cpu.svc`|context|monotonic over time|time spent servicing, outside of event loop wait|\n+`n.cn.dns`|context|go/no-go mean|duration of blocking libc DNS lookup|\n+`n.cn.adns`|context|go/no-go mean|duration of SYS_ASYNC_DNS lws DNS lookup|\n+`n.cn.tcp`|context|go/no-go mean|duration of tcp connection until accept|\n+`n.cn.tls`|context|go/no-go mean|duration of tls connection until accept|\n+`n.http.txn`|context|go (2xx)/no-go mean|duration of lws http transaction|\n+`n.ss.conn`|context|go/no-go mean|duration of Secure Stream transaction|\n+`n.ss.cliprox.conn`|context|go/no-go mean|time taken for client -\u003e proxy connection|\n+`vh.[vh-name].rx`|vhost|go/no-go sum|received data on the vhost|\n+`vh.[vh-name].tx`|vhost|go/no-go sum|transmitted data on the vhost|\n+\n+#### Histogram metrics\n+|metric name|scope|type|meaning|\n+|---|---|---|---|\n+`n.cn.failures`|context|histogram|Histogram of connection attempt failure reasons|\n+\n+#### Connection failure histogram buckets\n+|Bucket name|Meaning|\n+|---|---|\n+`tls/invalidca`|Peer certificate CA signature missing or not trusted|\n+`tls/hostname`|Peer certificate CN or SAN doesn't match the endpoint we asked for|\n+`tls/notyetvalid`|Peer certificate start date is in the future (time wrong?)|\n+`tls/expired`|Peer certificate is expiry date is in the past|\n+`dns/badsrv`|No DNS result because couldn't talk to the server|\n+`dns/nxdomain`|No DNS result because server says no result|\n+\n+The `lws-minimal-secure-streams` example is able to report the aggregated\n+metrics at the end of execution, eg\n+\n+```\n+[2021/01/13 11:47:19:9145] U: my_metric_report: cpu.svc: 137.045ms / 884.563ms (15%)\n+[2021/01/13 11:47:19:9145] U: my_metric_report: n.cn.dns: Go: 4, mean: 3.792ms, min: 2.470ms, max: 5.426ms\n+[2021/01/13 11:47:19:9145] U: my_metric_report: n.cn.tcp: Go: 4, mean: 40.633ms, min: 17.107ms, max: 94.560ms\n+[2021/01/13 11:47:19:9145] U: my_metric_report: n.cn.tls: Go: 3, mean: 91.232ms, min: 30.322ms, max: 204.635ms\n+[2021/01/13 11:47:19:9145] U: my_metric_report: n.http.txn: Go: 4, mean: 63.089ms, min: 20.184ms, max: 125.474ms\n+[2021/01/13 11:47:19:9145] U: my_metric_report: n.ss.conn: Go: 4, mean: 161.740ms, min: 42.937ms, max: 429.510ms\n+[2021/01/13 11:47:19:9145] U: my_metric_report: vh._ss_default.rx: Go: (1) 102, NoGo: (1) 0\n+[2021/01/13 11:47:19:9145] U: my_metric_report: vh.le_via_dst.rx: Go: (22) 28.165Ki\n+[2021/01/13 11:47:19:9145] U: my_metric_report: vh.le_via_dst.tx: Go: (1) 267\n+[2021/01/13 11:47:19:9145] U: my_metric_report: vh.api_amazon_com.rx: Go: (1) 1.611Ki, NoGo: (1) 0\n+[2021/01/13 11:47:19:9145] U: my_metric_report: vh.api_amazon_com.tx: Go: (3) 1.505Ki\n+```\n+\n+lws-minimal-secure-stream-testsfail which tests various kinds of connection failure\n+reports histogram results like this\n+\n+```\n+[2021/01/15 13:10:16:0933] U: my_metric_report: n.cn.failures: tot: 36, [ tls/invalidca: 5, tls/expired: 5, tls/hostname: 5, dns/nxdomain: 21 ]\n+```\n+\n+## Support for openmetrics\n+\n+Openmetrics https://tools.ietf.org/html/draft-richih-opsawg-openmetrics-00\n+defines a textual metrics export format comaptible with Prometheus. Lws\n+provides a protocol plugin in `./plugins/protocol_lws_openmetrics_export`\n+that enables direct export for prometheus scraping, and also protocols to\n+proxy openmetrics export for unreachable servers.\ndiff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in\nindex 3d4b867..cd9aa2b 100644\n--- a/cmake/lws_config.h.in\n+++ b/cmake/lws_config.h.in\n@@ -195,6 +195,7 @@\n #cmakedefine LWS_WITH_SQLITE3\n #cmakedefine LWS_WITH_SYS_DHCP_CLIENT\n #cmakedefine LWS_WITH_SYS_FAULT_INJECTION\n+#cmakedefine LWS_WITH_SYS_METRICS\n #cmakedefine LWS_WITH_SYS_NTPCLIENT\n #cmakedefine LWS_WITH_SYS_STATE\n #cmakedefine LWS_WITH_THREADPOOL\ndiff --git a/doc-assets/lws_metrics-decimation.png b/doc-assets/lws_metrics-decimation.png\nnew file mode 100644\nindex 0000000..e791608\nBinary files /dev/null and b/doc-assets/lws_metrics-decimation.png differ\ndiff --git a/doc-assets/lws_metrics-policy.png b/doc-assets/lws_metrics-policy.png\nnew file mode 100644\nindex 0000000..d1d5766\nBinary files /dev/null and b/doc-assets/lws_metrics-policy.png differ\ndiff --git a/include/libwebsockets.h b/include/libwebsockets.h\nindex ae4ab6b..63dfa4f 100644\n--- a/include/libwebsockets.h\n+++ b/include/libwebsockets.h\n@@ -575,8 +575,8 @@ struct lws;\n #include \u003clibwebsockets/lws-retry.h\u003e\n #include \u003clibwebsockets/lws-adopt.h\u003e\n #include \u003clibwebsockets/lws-network-helper.h\u003e\n+#include \u003clibwebsockets/lws-metrics.h\u003e\n #include \u003clibwebsockets/lws-system.h\u003e\n-#include \u003clibwebsockets/lws-detailed-latency.h\u003e\n #include \u003clibwebsockets/lws-ws-close.h\u003e\n #include \u003clibwebsockets/lws-callbacks.h\u003e\n #include \u003clibwebsockets/lws-ws-state.h\u003e\n@@ -605,7 +605,6 @@ struct lws;\n #include \u003clibwebsockets/lws-vfs.h\u003e\n #endif\n #include \u003clibwebsockets/lws-lejp.h\u003e\n-#include \u003clibwebsockets/lws-stats.h\u003e\n #include \u003clibwebsockets/lws-struct.h\u003e\n #include \u003clibwebsockets/lws-threadpool.h\u003e\n #include \u003clibwebsockets/lws-tokenize.h\u003e\ndiff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h\nindex b90876a..c72ded0 100644\n--- a/include/libwebsockets/lws-context-vhost.h\n+++ b/include/libwebsockets/lws-context-vhost.h\n@@ -245,6 +245,7 @@\n struct lws_plat_file_ops;\n struct lws_ss_policy;\n struct lws_ss_plugin;\n+struct lws_metric_policy;\n \n typedef int (*lws_context_ready_cb_t)(struct lws_context *context);\n \n@@ -725,13 +726,6 @@ struct lws_context_creation_info {\n \tconst lws_system_ops_t *system_ops;\n \t/**\u003c CONTEXT: hook up lws_system_ apis to system-specific\n \t * implementations */\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tdet_lat_buf_cb_t detailed_latency_cb;\n-\t/**\u003c CONTEXT: NULL, or callback to receive detailed latency information\n-\t * collected for each read and write */\n-\tconst char *detailed_latency_filepath;\n-\t/**\u003c CONTEXT: NULL, or filepath to put latency data into */\n-#endif\n \tconst lws_retry_bo_t *retry_and_idle_policy;\n \t/**\u003c VHOST: optional retry and idle policy to apply to this vhost.\n \t * Currently only the idle parts are applied to the connections.\n@@ -840,6 +834,17 @@ struct lws_context_creation_info {\n \t * (20 for FREERTOS) */\n #endif\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tconst struct lws_metric_policy\t\t*metrics_policies;\n+\t/**\u003c non-SS policy metrics policies */\n+\tconst char\t\t\t\t*metrics_prefix;\n+\t/**\u003c prefix for this context's metrics, used to distinguish metrics\n+\t * pooled from different processes / applications, so, eg what would\n+\t * be \u0022cpu.svc\u0022 if this is NULL becomes \u0022myapp.cpu.svc\u0022 is this is\n+\t * set to \u0022myapp\u0022. Policies are applied using the name with the prefix,\n+\t * if present.\n+\t */\n+#endif\n \n \t/* Add new things just above here ---^\n \t * This is part of the ABI, don't needlessly break compatibility\ndiff --git a/include/libwebsockets/lws-detailed-latency.h b/include/libwebsockets/lws-detailed-latency.h\ndeleted file mode 100644\nindex 1b352c7..0000000\n--- a/include/libwebsockets/lws-detailed-latency.h\n+++ /dev/null\n@@ -1,140 +0,0 @@\n-/*\n- * libwebsockets - small server side websockets and web server implementation\n- *\n- * Copyright (C) 2010 - 2019 Andy Green \u003candy@warmcat.com\u003e\n- *\n- * Permission is hereby granted, free of charge, to any person obtaining a copy\n- * of this software and associated documentation files (the \u0022Software\u0022), to\n- * deal in the Software without restriction, including without limitation the\n- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n- * sell copies of the Software, and to permit persons to whom the Software is\n- * furnished to do so, subject to the following conditions:\n- *\n- * The above copyright notice and this permission notice shall be included in\n- * all copies or substantial portions of the Software.\n- *\n- * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n- * IN THE SOFTWARE.\n- *\n- * included from libwebsockets.h\n- */\n-\n-enum {\n-\n-\t/* types of latency, all nonblocking except name resolution */\n-\n-\tLDLT_READ,\t/* time taken to read LAT_DUR_PROXY_RX_TO_CLIENT_WRITE */\n-\tLDLT_WRITE,\n-\tLDLT_NAME_RESOLUTION, /* BLOCKING: LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE */\n-\tLDLT_CONNECTION, /* conn duration: LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE */\n-\tLDLT_TLS_NEG_CLIENT, /* tls conn duration: LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE */\n-\tLDLT_TLS_NEG_SERVER, /* tls conn duration: LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE */\n-\n-\tLDLT_USER,\n-\n-\t/* interval / duration elements in latencies array */\n-\n-\tLAT_DUR_PROXY_CLIENT_REQ_TO_WRITE\t\t\t\t\u003d 0,\n-\t\t/* us the client spent waiting to write to proxy */\n-\tLAT_DUR_PROXY_CLIENT_WRITE_TO_PROXY_RX,\n-\t\t/* us the packet took to be received by proxy */\n-\tLAT_DUR_PROXY_PROXY_REQ_TO_WRITE,\n-\t\t/* us the proxy has to wait before it could write */\n-\tLAT_DUR_PROXY_RX_TO_ONWARD_TX,\n-\t\t/* us the proxy spent waiting to write to destination, or\n-\t\t * if nonproxied, then time between write request and write */\n-\n-\tLAT_DUR_USERCB, /* us duration of user callback */\n-\n-\tLAT_DUR_STEPS /* last */\n-};\n-\n-typedef struct lws_detlat {\n-\tlws_usec_t\t\tearliest_write_req;\n-\tlws_usec_t\t\tearliest_write_req_pre_write;\n-\t\t/**\u003c use this for interval comparison */\n-\tconst char\t\t*aux; /* name for name resolution timing */\n-\tint\t\t\ttype;\n-\tuint32_t\t\tlatencies[LAT_DUR_STEPS];\n-\tsize_t\t\t\treq_size;\n-\tsize_t\t\t\tacc_size;\n-} lws_detlat_t;\n-\n-typedef int (*det_lat_buf_cb_t)(struct lws_context *context,\n-\t\t\t\tconst lws_detlat_t *d);\n-\n-/**\n- * lws_det_lat_cb() - inject your own latency records\n- *\n- * \u005cparam context: the lws_context\n- * \u005cparam d: the lws_detlat_t you have prepared\n- *\n- * For proxying or similar cases where latency information is available from\n- * user code rather than lws itself, you can generate your own latency callback\n- * events with your own lws_detlat_t.\n- */\n-\n-LWS_VISIBLE LWS_EXTERN int\n-lws_det_lat_cb(struct lws_context *context, lws_detlat_t *d);\n-\n-/*\n- * detailed_latency_plot_cb() - canned save to file in plottable format cb\n- *\n- * \u005cp context: the lws_context\n- * \u005cp d: the detailed latency event information\n- *\n- * This canned callback makes it easy to export the detailed latency information\n- * to a file. Just set the context creation members like this\n- *\n- * #if defined(LWS_WITH_DETAILED_LATENCY)\n- *\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n- *\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-results\u0022;\n- * #endif\n- *\n- * and you will get a file containing information like this\n- *\n- * 718823864615 N 10589 0 0 10589 0 0 0\n- * 718823880837 C 16173 0 0 16173 0 0 0\n- * 718823913063 T 32212 0 0 32212 0 0 0\n- * 718823931835 r 0 0 0 0 232 30 256\n- * 718823948757 r 0 0 0 0 40 30 256\n- * 718823948799 r 0 0 0 0 83 30 256\n- * 718823965602 r 0 0 0 0 27 30 256\n- * 718823965617 r 0 0 0 0 43 30 256\n- * 718823965998 r 0 0 0 0 12 28 256\n- * 718823983887 r 0 0 0 0 74 3 4096\n- * 718823986411 w 16 87 7 110 9 80 80\n- * 718824006358 w 8 68 6 82 6 80 80\n- *\n- * which is easy to grep and pass to gnuplot.\n- *\n- * The columns are\n- *\n- * - unix time in us\n- * - N \u003d Name resolution, C \u003d TCP Connection, T \u003d TLS negotiation server,\n- * t \u003d TLS negotiation client, r \u003d Read, w \u003d Write\n- * - us duration, for w time client spent waiting to write\n- * - us duration, for w time data spent in transit to proxy\n- * - us duration, for w time proxy waited to send data\n- * - as a convenience, sum of last 3 columns above\n- * - us duration, time spent in callback\n- * - last 2 are actual / requested size in bytes\n- */\n-LWS_VISIBLE LWS_EXTERN int\n-lws_det_lat_plot_cb(struct lws_context *context, const lws_detlat_t *d);\n-\n-/**\n- * lws_det_lat_active() - indicates if latencies are being measured\n- *\n- * \u005ccontext: lws_context\n- *\n- * Returns 0 if latency measurement has not been set up (the callback is NULL).\n- * Otherwise returns 1\n- */\n-LWS_VISIBLE LWS_EXTERN int\n-lws_det_lat_active(struct lws_context *context);\ndiff --git a/include/libwebsockets/lws-lejp.h b/include/libwebsockets/lws-lejp.h\nindex 55c4dc0..6ea6d3d 100644\n--- a/include/libwebsockets/lws-lejp.h\n+++ b/include/libwebsockets/lws-lejp.h\n@@ -182,7 +182,7 @@ typedef signed char (*lejp_callback)(struct lejp_ctx *ctx, char reason);\n #define LEJP_MAX_DEPTH 12\n #endif\n #ifndef LEJP_MAX_INDEX_DEPTH\n-#define LEJP_MAX_INDEX_DEPTH 6\n+#define LEJP_MAX_INDEX_DEPTH 8\n #endif\n #ifndef LEJP_MAX_PATH\n #define LEJP_MAX_PATH 128\ndiff --git a/include/libwebsockets/lws-metrics.h b/include/libwebsockets/lws-metrics.h\nnew file mode 100644\nindex 0000000..4df7a26\n--- /dev/null\n+++ b/include/libwebsockets/lws-metrics.h\n@@ -0,0 +1,329 @@\n+ /*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2010 - 2021 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ *\n+ * Public apis related to metric collection and reporting\n+ */\n+\n+/* lws_metrics public part */\n+\n+typedef uint64_t u_mt_t;\n+\n+enum {\n+\tLWSMTFL_REPORT_OUTLIERS\t\t\t\t\u003d (1 \u003c\u003c 0),\n+\t/**\u003c track outliers and report them internally */\n+\tLWSMTFL_REPORT_OOB\t\t\t\t\u003d (1 \u003c\u003c 1),\n+\t/**\u003c report events as they happen */\n+\tLWSMTFL_REPORT_INACTIVITY_AT_PERIODIC\t\t\u003d (1 \u003c\u003c 2),\n+\t/**\u003c explicitly externally report no activity at periodic cb, by\n+\t * default no events in the period is just not reported */\n+\tLWSMTFL_REPORT_MEAN\t\t\t\t\u003d (1 \u003c\u003c 3),\n+\t/**\u003c average/min/max is meaningful, else only sum is meaningful */\n+\tLWSMTFL_REPORT_ONLY_GO\t\t\t\t\u003d (1 \u003c\u003c 4),\n+\t/**\u003c no-go pieces invalid */\n+\tLWSMTFL_REPORT_DUTY_WALLCLOCK_US\t\t\u003d (1 \u003c\u003c 5),\n+\t/**\u003c aggregate compares to wallclock us for duty cycle */\n+\tLWSMTFL_REPORT_HIST\t\t\t\t\u003d (1 \u003c\u003c 6),\n+\t/**\u003c our type is histogram (otherwise, sum / mean aggregation) */\n+};\n+\n+/*\n+ * lws_metrics_tag allows your object to accumulate OpenMetrics-style\n+ * descriptive tags before accounting for it with a metrics object at the end.\n+ *\n+ * Tags should represent low entropy information that is likely to repeat\n+ * identically, so, eg, http method name, not eg, latency in us which is\n+ * unlikely to be seen the same twice.\n+ *\n+ * Tags are just a list of name\u003dvalue pairs, used for qualifying the final\n+ * metrics entry with decorations in additional dimensions. For example,\n+ * rather than keep individual metrics on methods, scheme, mountpoint, result\n+ * code, you can keep metrics on http transactions only, and qualify the\n+ * transaction metrics entries with tags that can be queried on the metrics\n+ * backend to get the finer-grained information.\n+ *\n+ * http_srv{code\u003d\u0022404\u0022,mount\u003d\u0022/\u0022,method\u003d\u0022GET\u0022,scheme\u003d\u0022http\u0022} 3\n+ *\n+ * For OpenMetrics the tags are converted to a { list } and appended to the base\n+ * metrics name before using with actual metrics objects, the same set of tags\n+ * on different transactions resolve to the same qualification string.\n+ */\n+\n+typedef struct lws_metrics_tag {\n+\tlws_dll2_t\tlist;\n+\n+\tconst char\t*name; /* tag, intended to be in .rodata, not copied */\n+\t/* overallocated value */\n+} lws_metrics_tag_t;\n+\n+LWS_EXTERN LWS_VISIBLE int\n+lws_metrics_tag_add(lws_dll2_owner_t *owner, const char *name, const char *val);\n+\n+#if defined(LWS_WITH_SYS_METRICS)\n+/*\n+ * wsi-specific version that also appends the tag value to the lifecycle tag\n+ * used for logging the wsi identity\n+ */\n+LWS_EXTERN LWS_VISIBLE int\n+lws_metrics_tag_wsi_add(struct lws *wsi, const char *name, const char *val);\n+#else\n+#define lws_metrics_tag_wsi_add(_a, _b, _c)\n+#endif\n+\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+/*\n+ * ss-specific version that also appends the tag value to the lifecycle tag\n+ * used for logging the ss identity\n+ */\n+#if defined(LWS_WITH_SYS_METRICS)\n+LWS_EXTERN LWS_VISIBLE int\n+lws_metrics_tag_ss_add(struct lws_ss_handle *ss, const char *name, const char *val);\n+#else\n+#define lws_metrics_tag_ss_add(_a, _b, _c)\n+#endif\n+#endif\n+\n+LWS_EXTERN LWS_VISIBLE void\n+lws_metrics_tags_destroy(lws_dll2_owner_t *owner);\n+\n+LWS_EXTERN LWS_VISIBLE size_t\n+lws_metrics_tags_serialize(lws_dll2_owner_t *owner, char *buf, size_t len);\n+\n+LWS_EXTERN LWS_VISIBLE const char *\n+lws_metrics_tag_get(lws_dll2_owner_t *owner, const char *name);\n+\n+/* histogram bucket */\n+\n+typedef struct lws_metric_bucket {\n+\tstruct lws_metric_bucket\t*next;\n+\tuint64_t\t\t\tcount;\n+\n+\t/* name + NUL is overallocated */\n+} lws_metric_bucket_t;\n+\n+/* get overallocated name of bucket from bucket pointer */\n+#define lws_metric_bucket_name_len(_b) (*((uint8_t *)\u0026(_b)[1]))\n+#define lws_metric_bucket_name(_b) (((const char *)\u0026(_b)[1]) + 1)\n+\n+/*\n+ * These represent persistent local event measurements. They may aggregate\n+ * a large number of events inbetween external dumping of summaries of the\n+ * period covered, in two different ways\n+ *\n+ * 1) aggregation by sum or mean, to absorb multiple scalar readings\n+ *\n+ * - go / no-go ratio counting\n+ * - mean averaging for, eg, latencies\n+ * - min / max for averaged values\n+ * - period the stats covers\n+ *\n+ * 2) aggregation by histogram, to absorb a range of outcomes that may occur\n+ * multiple times\n+ *\n+ * - add named buckets to histogram\n+ * - bucket has a 64-bit count\n+ * - bumping a bucket just increments the count if already exists, else adds\n+ * a new one with count set to 1\n+ *\n+ * The same type with a union covers both cases.\n+ *\n+ * The lws_system ops api that hooks lws_metrics up to a metrics backend is\n+ * given a pointer to these according to the related policy, eg, hourly, or\n+ * every event passed straight through.\n+ */\n+\n+typedef struct lws_metric_pub {\n+\tconst char\t\t*name;\n+\t/**\u003c eg, \u0022n.cn.dns\u0022, \u0022vh.myendpoint\u0022 */\n+\tvoid\t\t\t*backend_opaque;\n+\t/**\u003c ignored by lws, backend handler completely owns it */\n+\n+\tlws_usec_t\t\tus_first;\n+\t/**\u003c us time metric started collecting, reset to us_dumped at dump */\n+\tlws_usec_t\t\tus_last;\n+\t/**\u003c 0, or us time last event, reset to 0 at last dump */\n+\tlws_usec_t\t\tus_dumped;\n+\t/**\u003c 0 if never, else us time of last dump to external api */\n+\n+\t/* scope of data in .u is \u0022since last dump\u0022 --\u003e */\n+\n+\tunion {\n+\t\t/* aggregation, by sum or mean */\n+\n+\t\tstruct {\n+\t\t\tu_mt_t\t\t\tsum[2];\n+\t\t\t/**\u003c go, no-go summed for mean or plan sum */\n+\t\t\tu_mt_t\t\t\tmin;\n+\t\t\t/**\u003c smallest individual measurement */\n+\t\t\tu_mt_t\t\t\tmax;\n+\t\t\t/**\u003c largest individual measurement */\n+\n+\t\t\tuint32_t\t\tcount[2];\n+\t\t\t/**\u003c go, no-go count of measurements in sum */\n+\t\t} agg;\n+\n+\t\t/* histogram with dynamic named buckets */\n+\n+\t\tstruct {\n+\t\t\tlws_metric_bucket_t\t*head;\n+\t\t\t/**\u003c first bucket in our bucket list */\n+\n+\t\t\tuint64_t\t\ttotal_count;\n+\t\t\t/**\u003c total count in all of our buckets */\n+\t\t\tuint32_t\t\tlist_size;\n+\t\t\t/**\u003c number of buckets in our bucket list */\n+\t\t} hist;\n+\t} u;\n+\n+\tuint8_t\t\t\tflags;\n+\n+} lws_metric_pub_t;\n+\n+LWS_EXTERN LWS_VISIBLE void\n+lws_metrics_hist_bump_priv_tagged(lws_metric_pub_t *mt, lws_dll2_owner_t *tow,\n+\t\t\t\t lws_dll2_owner_t *tow2);\n+\n+\n+/*\n+ * Calipers are a helper struct for implementing \u0022hanging latency\u0022 detection,\n+ * where setting the start time and finding the end time may happen in more than\n+ * one place.\n+ *\n+ * There are convenience wrappers to eliminate caliper definitions and code\n+ * cleanly if WITH_SYS_METRICS is disabled for the build.\n+ */\n+\n+struct lws_metric;\n+\n+typedef struct lws_metric_caliper {\n+\tstruct lws_dll2_owner\tmtags_owner; /**\u003c collect tags here during\n+\t\t\t\t\t * caliper lifetime */\n+\tstruct lws_metric\t*mt; /**\u003c NULL \u003d\u003d inactive */\n+\tlws_usec_t\t\tus_start;\n+} lws_metric_caliper_t;\n+\n+#if defined(LWS_WITH_SYS_METRICS)\n+#define lws_metrics_caliper_compose(_name) \u005c\n+\t\tlws_metric_caliper_t _name;\n+#define lws_metrics_caliper_bind(_name, _mt) \u005c\n+\t{ if (_name.mt) { \u005c\n+\t\tlwsl_err(\u0022caliper: overwrite %s\u005cn\u0022, \u005c\n+\t\t\t\tlws_metrics_priv_to_pub(_name.mt)-\u003ename); \u005c\n+\t\tassert(0); } \u005c\n+\t _name.mt \u003d _mt; _name.us_start \u003d lws_now_usecs(); }\n+#define lws_metrics_caliper_declare(_name, _mt) \u005c\n+\tlws_metric_caliper_t _name \u003d { .mt \u003d _mt, .us_start \u003d lws_now_usecs() }\n+#define lws_metrics_caliper_report(_name, _go_nogo) \u005c\n+\t{ if (_name.us_start) { lws_metric_event(_name.mt, _go_nogo, \u005c\n+\t\t\t (u_mt_t)(lws_now_usecs() - \u005c\n+\t\t\t\t\t _name.us_start)); \u005c\n+\t\t\t\t\t } lws_metrics_caliper_done(_name); }\n+#define lws_metrics_caliper_report_hist(_name, pwsi) if (_name.mt) { \u005c\n+\t\tlws_metrics_hist_bump_priv_tagged(lws_metrics_priv_to_pub(_name.mt), \u005c\n+\t\t\t\t\t\t \u0026_name.mtags_owner, \u005c\n+\t\t\t\t\t\t pwsi ? \u0026((pwsi)-\u003ecal_conn.mtags_owner) : NULL); \u005c\n+\t\tlws_metrics_caliper_done(_name); }\n+\n+#define lws_metrics_caliper_cancel(_name) { lws_metrics_caliper_done(_name); }\n+#define lws_metrics_hist_bump(_mt, _name) \u005c\n+\t\tlws_metrics_hist_bump_(_mt, _name)\n+#define lws_metrics_hist_bump_priv(_mt, _name) \u005c\n+\t\tlws_metrics_hist_bump_(lws_metrics_priv_to_pub(_mt), _name)\n+#define lws_metrics_caliper_done(_name) { \u005c\n+\t\t_name.us_start \u003d 0; _name.mt \u003d NULL; \u005c\n+\t\tlws_metrics_tags_destroy(\u0026_name.mtags_owner); }\n+#else\n+#define lws_metrics_caliper_compose(_name)\n+#define lws_metrics_caliper_bind(_name, _mt)\n+#define lws_metrics_caliper_declare(_name, _mp)\n+#define lws_metrics_caliper_report(_name, _go_nogo)\n+#define lws_metrics_caliper_report_hist(_name, pwsiconn)\n+#define lws_metrics_caliper_cancel(_name)\n+#define lws_metrics_hist_bump(_mt, _name)\n+#define lws_metrics_hist_bump_priv(_mt, _name)\n+#define lws_metrics_caliper_done(_name)\n+#endif\n+\n+/**\n+ * lws_metrics_format() - helper to format a metrics object for logging\n+ *\n+ * \u005cparam pub: public part of metrics object\n+ * \u005cparam buf: output buffer to place string in\n+ * \u005cparam len: available length of \u005cp buf\n+ *\n+ * Helper for describing the state of a metrics object as a human-readable\n+ * string, accounting for how its flags indicate what it contains. This is not\n+ * how you would report metrics, but during development it can be useful to\n+ * log them inbetween possibily long report intervals.\n+ *\n+ * It uses the metric's flags to adapt the format shown appropriately, eg,\n+ * as a histogram if LWSMTFL_REPORT_HIST etc\n+ */\n+LWS_EXTERN LWS_VISIBLE int\n+lws_metrics_format(lws_metric_pub_t *pub, lws_metric_bucket_t **sub,\n+\t\t char *buf, size_t len);\n+\n+/**\n+ * lws_metrics_hist_bump() - add or increment histogram bucket\n+ *\n+ * \u005cparam pub: public part of metrics object\n+ * \u005cparam name: bucket name to increment\n+ *\n+ * Either increment the count of an existing bucket of the right name in the\n+ * metrics object, or add a new bucket of the given name and set its count to 1.\n+ *\n+ * The metrics object must have been created with flag LWSMTFL_REPORT_HIST\n+ *\n+ * Normally, you will actually use the preprocessor wrapper\n+ * lws_metrics_hist_bump() defined above, since this automatically takes care of\n+ * removing itself from the build if WITH_SYS_METRICS is not defined, without\n+ * needing any preprocessor conditionals.\n+ */\n+LWS_EXTERN LWS_VISIBLE int\n+lws_metrics_hist_bump_(lws_metric_pub_t *pub, const char *name);\n+\n+LWS_VISIBLE LWS_EXTERN int\n+lws_metrics_foreach(struct lws_context *ctx, void *user,\n+\t\t int (*cb)(lws_metric_pub_t *pub, void *user));\n+\n+LWS_VISIBLE LWS_EXTERN int\n+lws_metrics_hist_bump_describe_wsi(struct lws *wsi, lws_metric_pub_t *pub,\n+\t\t\t\t const char *name);\n+\n+enum {\n+\tLMT_NORMAL \u003d 0,\t/* related to successful events */\n+\tLMT_OUTLIER,\t/* related to successful events outside of bounds */\n+\n+\tLMT_FAIL,\t/* related to failed events */\n+\n+\tLMT_COUNT,\n+};\n+\n+typedef enum lws_metric_rpt {\n+\tLMR_PERIODIC \u003d 0,\t/* we are reporting on a schedule */\n+\tLMR_OUTLIER,\t\t/* we are reporting the last outlier */\n+} lws_metric_rpt_kind_t;\n+\n+#define METRES_GO\t0\n+#define METRES_NOGO\t1\n+\n+\ndiff --git a/include/libwebsockets/lws-protocols-plugins.h b/include/libwebsockets/lws-protocols-plugins.h\nindex b39bf3b..ec9d8d8 100644\n--- a/include/libwebsockets/lws-protocols-plugins.h\n+++ b/include/libwebsockets/lws-protocols-plugins.h\n@@ -359,7 +359,22 @@ extern const struct lws_protocols lws_sshd_demo_protocols[1];\n extern const struct lws_protocols lws_acme_client_protocols[1];\n extern const struct lws_protocols client_loopback_test_protocols[1];\n extern const struct lws_protocols fulltext_demo_protocols[1];\n+extern const struct lws_protocols lws_openmetrics_export_protocols[\n+#if defined(LWS_WITH_SERVER) \u0026\u0026 defined(LWS_WITH_CLIENT) \u0026\u0026 defined(LWS_ROLE_WS)\n+\t4\n+#else\n+#if defined(LWS_WITH_SERVER)\n+\t3\n+#else\n+\t1\n+#endif\n+#endif\n+\t];\n \n+#define LWSOMPROIDX_DIRECT_HTTP_SERVER\t\t0\n+#define LWSOMPROIDX_PROX_HTTP_SERVER\t\t1\n+#define LWSOMPROIDX_PROX_WS_SERVER\t\t2\n+#define LWSOMPROIDX_PROX_WS_CLIENT\t\t3\n \n #endif\n \ndiff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h\nindex d22b23e..f392460 100644\n--- a/include/libwebsockets/lws-secure-streams-policy.h\n+++ b/include/libwebsockets/lws-secure-streams-policy.h\n@@ -77,6 +77,25 @@ typedef struct lws_ss_plugin {\n } lws_ss_plugin_t;\n #endif\n \n+/* the public, const metrics policy definition */\n+\n+typedef struct lws_metric_policy {\n+\t/* order of first two mandated by JSON policy parsing scope union */\n+\tconst struct lws_metric_policy\t*next;\n+\tconst char\t\t\t*name;\n+\n+\tconst char\t\t\t*report;\n+\n+\t/**\u003c the metrics policy name in the policy, used to bind to it */\n+\tuint32_t\t\t\tus_schedule;\n+\t/**\u003c us interval between lws_system metrics api reports */\n+\n+\tuint32_t\t\t\tus_decay_unit;\n+\t/**\u003c how many us to decay avg by half, 0 \u003d no decay */\n+\tuint8_t\t\t\t\tmin_contributors;\n+\t/**\u003c before we can judge something is an outlier */\n+} lws_metric_policy_t;\n+\n typedef struct lws_ss_x509 {\n \tstruct lws_ss_x509\t*next;\n \tconst char\t\t*vhost_name; /**\u003c vhost name using cert ctx */\n@@ -226,6 +245,7 @@ typedef struct lws_ss_policy {\n \tconst char\t\t*payload_fmt;\n \tconst char\t\t*socks5_proxy;\n \tlws_ss_metadata_t\t*metadata; /* linked-list of metadata */\n+\tconst lws_metric_policy_t *metrics; /* linked-list of metric policies */\n \tconst lws_ss_auth_t\t*auth; /* NULL or auth object we bind to */\n \n \t/* protocol-specific connection policy details */\ndiff --git a/include/libwebsockets/lws-smd.h b/include/libwebsockets/lws-smd.h\nindex f5188b5..50dbc9e 100644\n--- a/include/libwebsockets/lws-smd.h\n+++ b/include/libwebsockets/lws-smd.h\n@@ -52,6 +52,11 @@ enum {\n \t * Something happened on the network, eg, link-up or DHCP, or captive\n \t * portal state update\n \t */\n+\tLWSSMDCL_METRICS\t\t\t\t\t\u003d (1 \u003c\u003c 3),\n+\t/**\u003c\n+\t * An SS client process is reporting a metric to the proxy (this class\n+\t * is special in that it is not rebroadcast by the proxy)\n+\t */\n \n \tLWSSMDCL_USER_BASE_BITNUM\t\t\t\t\u003d 24\n };\ndiff --git a/include/libwebsockets/lws-stats.h b/include/libwebsockets/lws-stats.h\ndeleted file mode 100644\nindex ca9a4e6..0000000\n--- a/include/libwebsockets/lws-stats.h\n+++ /dev/null\n@@ -1,81 +0,0 @@\n-/*\n- * libwebsockets - small server side websockets and web server implementation\n- *\n- * Copyright (C) 2010 - 2019 Andy Green \u003candy@warmcat.com\u003e\n- *\n- * Permission is hereby granted, free of charge, to any person obtaining a copy\n- * of this software and associated documentation files (the \u0022Software\u0022), to\n- * deal in the Software without restriction, including without limitation the\n- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n- * sell copies of the Software, and to permit persons to whom the Software is\n- * furnished to do so, subject to the following conditions:\n- *\n- * The above copyright notice and this permission notice shall be included in\n- * all copies or substantial portions of the Software.\n- *\n- * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n- * IN THE SOFTWARE.\n- */\n-\n-/*\n- * Stats are all uint64_t numbers that start at 0.\n- * Index names here have the convention\n- *\n- * _C_ counter\n- * _B_ byte count\n- * _MS_ millisecond count\n- */\n-\n-enum {\n-\tLWSSTATS_C_CONNECTIONS, /**\u003c count incoming connections */\n-\tLWSSTATS_C_API_CLOSE, /**\u003c count calls to close api */\n-\tLWSSTATS_C_API_READ, /**\u003c count calls to read from socket api */\n-\tLWSSTATS_C_API_LWS_WRITE, /**\u003c count calls to lws_write API */\n-\tLWSSTATS_C_API_WRITE, /**\u003c count calls to write API */\n-\tLWSSTATS_C_WRITE_PARTIALS, /**\u003c count of partial writes */\n-\tLWSSTATS_C_WRITEABLE_CB_REQ, /**\u003c count of writable callback requests */\n-\tLWSSTATS_C_WRITEABLE_CB_EFF_REQ, /**\u003c count of effective writable callback requests */\n-\tLWSSTATS_C_WRITEABLE_CB, /**\u003c count of writable callbacks */\n-\tLWSSTATS_C_SSL_CONNECTIONS_FAILED, /**\u003c count of failed SSL connections */\n-\tLWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, /**\u003c count of accepted SSL connections */\n-\tLWSSTATS_C_SSL_ACCEPT_SPIN, /**\u003c count of SSL_accept() attempts */\n-\tLWSSTATS_C_SSL_CONNS_HAD_RX, /**\u003c count of accepted SSL conns that have had some RX */\n-\tLWSSTATS_C_TIMEOUTS, /**\u003c count of timed-out connections */\n-\tLWSSTATS_C_SERVICE_ENTRY, /**\u003c count of entries to lws service loop */\n-\tLWSSTATS_B_READ, /**\u003c aggregate bytes read */\n-\tLWSSTATS_B_WRITE, /**\u003c aggregate bytes written */\n-\tLWSSTATS_B_PARTIALS_ACCEPTED_PARTS, /**\u003c aggreate of size of accepted write data from new partials */\n-\tLWSSTATS_US_SSL_ACCEPT_LATENCY_AVG, /**\u003c aggregate delay in accepting connection */\n-\tLWSSTATS_US_WRITABLE_DELAY_AVG, /**\u003c aggregate delay between asking for writable and getting cb */\n-\tLWSSTATS_US_WORST_WRITABLE_DELAY, /**\u003c single worst delay between asking for writable and getting cb */\n-\tLWSSTATS_US_SSL_RX_DELAY_AVG, /**\u003c aggregate delay between ssl accept complete and first RX */\n-\tLWSSTATS_C_PEER_LIMIT_AH_DENIED, /**\u003c number of times we would have given an ah but for the peer limit */\n-\tLWSSTATS_C_PEER_LIMIT_WSI_DENIED, /**\u003c number of times we would have given a wsi but for the peer limit */\n-\tLWSSTATS_C_CONNS_CLIENT, /**\u003c attempted client conns */\n-\tLWSSTATS_C_CONNS_CLIENT_FAILED, /**\u003c failed client conns */\n-\n-\t/* Add new things just above here ---^\n-\t * This is part of the ABI, don't needlessly break compatibility\n-\t *\n-\t * UPDATE stat_names in stats.c in sync with this!\n-\t */\n-\tLWSSTATS_SIZE\n-};\n-\n-#if defined(LWS_WITH_STATS)\n-\n-LWS_VISIBLE LWS_EXTERN uint64_t\n-lws_stats_get(struct lws_context *context, int index);\n-LWS_VISIBLE LWS_EXTERN void\n-lws_stats_log_dump(struct lws_context *context);\n-#else\n-static LWS_INLINE uint64_t\n-lws_stats_get(struct lws_context *context, int index) { (void)context; (void)index; return 0; }\n-static LWS_INLINE void\n-lws_stats_log_dump(struct lws_context *context) { (void)context; }\n-#endif\ndiff --git a/include/libwebsockets/lws-system.h b/include/libwebsockets/lws-system.h\nindex 1feef79..5d57a1f 100644\n--- a/include/libwebsockets/lws-system.h\n+++ b/include/libwebsockets/lws-system.h\n@@ -1,7 +1,7 @@\n /*\n * libwebsockets - small server side websockets and web server implementation\n *\n- * Copyright (C) 2010 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ * Copyright (C) 2010 - 2021 Andy Green \u003candy@warmcat.com\u003e\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \u0022Software\u0022), to\n@@ -151,7 +151,6 @@ typedef enum {\n \tLWS_CPD_NO_INTERNET,\t/* we couldn't touch anything */\n } lws_cpd_result_t;\n \n-\n typedef void (*lws_attach_cb_t)(struct lws_context *context, int tsi, void *opaque);\n struct lws_attach_item;\n \n@@ -182,6 +181,11 @@ typedef struct lws_system_ops {\n \t * by calling lws_captive_portal_detect_result() api\n \t */\n \n+\tint (*metric_report)(lws_metric_pub_t *mdata);\n+\t/**\u003c metric \u005cp item is reporting an event of kind \u005cp rpt,\n+\t * held in \u005cp mdata... return 0 to leave the metric object as it is,\n+\t * or nonzero to reset it. */\n+\n \tuint32_t\twake_latency_us;\n \t/**\u003c time taken for this device to wake from suspend, in us\n \t */\ndiff --git a/lib/core-net/CMakeLists.txt b/lib/core-net/CMakeLists.txt\nindex e401506..96c742e 100644\n--- a/lib/core-net/CMakeLists.txt\n+++ b/lib/core-net/CMakeLists.txt\n@@ -51,11 +51,6 @@ if (LWS_WITH_NETLINK)\n \t)\n endif()\n \n-if (LWS_WITH_DETAILED_LATENCY)\n-\tlist(APPEND SOURCES\n-\t\tcore-net/detailed-latency.c)\n-endif()\n-\n if (LWS_WITH_LWS_DSH)\n \tlist(APPEND SOURCES\n \t\tcore-net/lws-dsh.c)\n@@ -77,20 +72,9 @@ if (LWS_WITH_CLIENT)\n \t)\n endif()\n \n-if (NOT LWS_WITHOUT_SERVER)\n-\tlist(APPEND SOURCES\n-\t\tcore-net/server.c)\n-endif()\n-\n if (LWS_WITH_SOCKS5 AND NOT LWS_WITHOUT_CLIENT)\n \tlist(APPEND SOURCES\n \t\tcore-net/socks5-client.c)\n endif()\n \n-if (LWS_WITH_NETWORK AND LWS_WITH_STATS)\n-\tlist(APPEND SOURCES\n-\t\tcore-net/stats.c\n-\t)\n-endif()\n-\n exports_to_parent_scope()\ndiff --git a/lib/core-net/adopt.c b/lib/core-net/adopt.c\nindex 8c2c055..98c92d3 100644\n--- a/lib/core-net/adopt.c\n+++ b/lib/core-net/adopt.c\n@@ -66,7 +66,11 @@ lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi, const char *de\n \t\treturn NULL;\n \t}\n \n-\t__lws_lc_tag(\u0026vhost-\u003econtext-\u003elcg[LWSLCG_WSI_SERVER], \u0026new_wsi-\u003elc, desc);\n+\t__lws_lc_tag(\u0026vhost-\u003econtext-\u003elcg[\n+#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT)\n+\tstrcmp(desc, \u0022adopted\u0022) ? LWSLCG_WSI_MUX :\n+#endif\n+\tLWSLCG_WSI_SERVER], \u0026new_wsi-\u003elc, desc);\n \n \tnew_wsi-\u003ewsistate |\u003d LWSIFR_SERVER;\n \tnew_wsi-\u003etsi \u003d (char)n;\n@@ -77,11 +81,6 @@ lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi, const char *de\n \tnew_wsi-\u003erxflow_change_to \u003d LWS_RXFLOW_ALLOW;\n \tnew_wsi-\u003eretry_policy \u003d vhost-\u003eretry_policy;\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tif (vhost-\u003econtext-\u003edetailed_latency_cb)\n-\t\tnew_wsi-\u003edetlat.earliest_write_req_pre_write \u003d lws_now_usecs();\n-#endif\n-\n \t/* initialize the instance struct */\n \n \tlwsi_set_state(new_wsi, LRS_UNCONNECTED);\n@@ -145,8 +144,6 @@ lws_adopt_descriptor_vhost1(struct lws_vhost *vh, lws_adoption_type type,\n \tpt \u003d \u0026context-\u003ept[(int)new_wsi-\u003etsi];\n \tlws_pt_lock(pt, __func__);\n \n-\tlws_stats_bump(pt, LWSSTATS_C_CONNECTIONS, 1);\n-\n \tif (parent) {\n \t\tnew_wsi-\u003eparent \u003d parent;\n \t\tnew_wsi-\u003esibling_list \u003d parent-\u003echild_list;\n@@ -176,6 +173,11 @@ lws_adopt_descriptor_vhost1(struct lws_vhost *vh, lws_adoption_type type,\n \t\tgoto bail;\n \t}\n \n+#if defined(LWS_WITH_SERVER)\n+\tif (new_wsi-\u003erole_ops)\n+\t\tlws_metrics_tag_wsi_add(new_wsi, \u0022role\u0022, new_wsi-\u003erole_ops-\u003ename);\n+#endif\n+\n \tlws_pt_unlock(pt);\n \n \t/*\n@@ -494,9 +496,6 @@ lws_adopt_descriptor_vhost_via_info(const lws_adopt_desc_t *info)\n \t\t peer-\u003ecount_wsi \u003e\u003d info-\u003evh-\u003econtext-\u003eip_limit_wsi) {\n \t\t\tlwsl_info(\u0022Peer reached wsi limit %d\u005cn\u0022,\n \t\t\t\t\tinfo-\u003evh-\u003econtext-\u003eip_limit_wsi);\n-\t\t\tlws_stats_bump(\u0026info-\u003evh-\u003econtext-\u003ept[0],\n-\t\t\t\t\t LWSSTATS_C_PEER_LIMIT_WSI_DENIED,\n-\t\t\t\t\t 1);\n \t\t\tif (info-\u003evh-\u003econtext-\u003epl_notify_cb)\n \t\t\t\tinfo-\u003evh-\u003econtext-\u003epl_notify_cb(\n \t\t\t\t\t\t\tinfo-\u003evh-\u003econtext,\ndiff --git a/lib/core-net/client/connect.c b/lib/core-net/client/connect.c\nindex dd3ca35..edc4b9a 100644\n--- a/lib/core-net/client/connect.c\n+++ b/lib/core-net/client/connect.c\n@@ -86,8 +86,12 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)\n \tstruct lws *wsi, *safe \u003d NULL;\n \tconst struct lws_protocols *p;\n \tconst char *cisin[CIS_COUNT];\n-\tint tid \u003d 0, n, tsi \u003d 0;\n \tstruct lws_vhost *vh;\n+\tint\n+#if LWS_MAX_SMP \u003e 1\n+\t\ttid \u003d 0,\n+#endif\n+\t\tn, tsi \u003d 0;\n \tsize_t size;\n \tchar *pc;\n \n@@ -105,9 +109,6 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)\n \tif (i-\u003elocal_protocol_name)\n \t\tlocal \u003d i-\u003elocal_protocol_name;\n \n-\tlws_stats_bump(\u0026i-\u003econtext-\u003ept[tid], LWSSTATS_C_CONNS_CLIENT, 1);\n-\n-\n \tlws_context_lock(i-\u003econtext, __func__);\n \t/*\n \t * PHASE 1: if SMP, find out the tsi related to current service thread\n@@ -161,10 +162,6 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)\n \t\tlws_fi_import(\u0026wsi-\u003efi, i-\u003efi);\n #endif\n \n-#if defined(LWS_WITH_DETAILED_LATENCY) \u0026\u0026 LWS_MAX_SMP \u003e 1\n-\twsi-\u003edetlat.tsi \u003d tsi;\n-#endif\n-\n \t/*\n \t * Until we exit, we can report connection failure directly to the\n \t * caller without needing to call through to protocol CONNECTION_ERROR.\n@@ -186,11 +183,6 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)\n \telse\n \t\twsi-\u003eretry_policy \u003d \u0026i-\u003econtext-\u003edefault_retry;\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tif (i-\u003econtext-\u003edetailed_latency_cb)\n-\t\twsi-\u003edetlat.earliest_write_req_pre_write \u003d lws_now_usecs();\n-#endif\n-\n \tif (i-\u003essl_connection \u0026 LCCSCF_WAKE_SUSPEND__VALIDITY)\n \t\twsi-\u003econn_validity_wakesuspend \u003d 1;\n \n@@ -370,7 +362,8 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)\n \t\t\t\u0026wsi-\u003elc, \u0022%s/%s/%s/(%s)\u0022, i-\u003emethod ? i-\u003emethod : \u0022WS\u0022,\n \t\t\twsi-\u003erole_ops-\u003ename, i-\u003eaddress,\n #if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n-\t\t\twsi-\u003eclient_bound_sspc ? lws_sspc_tag((lws_sspc_handle_t *)i-\u003eopaque_user_data) :\n+\t\t\twsi-\u003eclient_bound_sspc ?\n+\t\t\t\tlws_sspc_tag((lws_sspc_handle_t *)i-\u003eopaque_user_data) :\n #endif\n \t\t\tlws_ss_tag(((lws_ss_handle_t *)i-\u003eopaque_user_data)));\n \t} else\n@@ -379,6 +372,8 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)\n \t\t\t \u0022%s/%s/%s\u0022, i-\u003emethod ? i-\u003emethod : \u0022WS\u0022,\n \t\t\t wsi-\u003erole_ops-\u003ename, i-\u003eaddress);\n \n+\tlws_metrics_tag_wsi_add(wsi, \u0022vh\u0022, wsi-\u003ea.vhost-\u003ename);\n+\n \tpc \u003d (char *)\u0026wsi-\u003estash[1];\n \n \tfor (n \u003d 0; n \u003c CIS_COUNT; n++)\n@@ -533,7 +528,5 @@ bail2:\n \tif (i-\u003epwsi)\n \t\t*i-\u003epwsi \u003d NULL;\n \n-\tlws_stats_bump(\u0026i-\u003econtext-\u003ept[tid], LWSSTATS_C_CONNS_CLIENT_FAILED, 1);\n-\n \treturn NULL;\n }\ndiff --git a/lib/core-net/client/connect2.c b/lib/core-net/client/connect2.c\nindex 9e1bbe5..2e70334 100644\n--- a/lib/core-net/client/connect2.c\n+++ b/lib/core-net/client/connect2.c\n@@ -32,7 +32,11 @@\n static int\n lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result)\n {\n+\tlws_metrics_caliper_declare(cal, wsi-\u003ea.context-\u003emt_conn_dns);\n \tstruct addrinfo hints;\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tchar buckname[32];\n+#endif\n \tint n;\n \n \tmemset(\u0026hints, 0, sizeof(hints));\n@@ -79,12 +83,26 @@ lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result)\n \n #endif\n \t\twsi-\u003edns_reachability \u003d 1;\n-\t\tlwsl_notice(\u0022%s: asking to recheck CPD in 1ms\u005cn\u0022, __func__);\n-\t\tlws_system_cpd_start_defer(wsi-\u003ea.context, LWS_US_PER_MS);\n+\t\tlws_metrics_caliper_report(cal, METRES_NOGO);\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tlws_snprintf(buckname, sizeof(buckname), \u0022dns\u003d\u005c\u0022unreachable %d\u005c\u0022\u0022, n);\n+\t\tlws_metrics_hist_bump_priv_wsi(wsi, mth_conn_failures, buckname);\n+#endif\n+\t\tlwsl_notice(\u0022%s: asking to recheck CPD in 1s\u005cn\u0022, __func__);\n+\t\tlws_system_cpd_start_defer(wsi-\u003ea.context, LWS_US_PER_SEC);\n \t}\n \n \tlwsl_info(\u0022%s: getaddrinfo '%s' says %d\u005cn\u0022, __func__, ads, n);\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tif (n \u003c 0) {\n+\t\tlws_snprintf(buckname, sizeof(buckname), \u0022dns\u003d\u005c\u0022nores %d\u005c\u0022\u0022, n);\n+\t\tlws_metrics_hist_bump_priv_wsi(wsi, mth_conn_failures, buckname);\n+\t}\n+#endif\n+\n+\tlws_metrics_caliper_report(cal, n \u003e\u003d 0 ? METRES_GO : METRES_NOGO);\n+\n \treturn n;\n }\n #endif\n@@ -260,19 +278,6 @@ solo:\n \t}\n #endif\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tif (lwsi_state(wsi) \u003d\u003d LRS_WAITING_DNS \u0026\u0026\n-\t wsi-\u003ea.context-\u003edetailed_latency_cb) {\n-\t\twsi-\u003edetlat.type \u003d LDLT_NAME_RESOLUTION;\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] \u003d\n-\t\t\t(uint32_t)(lws_now_usecs() -\n-\t\t\twsi-\u003edetlat.earliest_write_req_pre_write);\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d 0;\n-\t\tlws_det_lat_cb(wsi-\u003ea.context, \u0026wsi-\u003edetlat);\n-\t\twsi-\u003edetlat.earliest_write_req_pre_write \u003d lws_now_usecs();\n-\t}\n-#endif\n-\n #if defined(LWS_CLIENT_HTTP_PROXYING) \u0026\u0026 \u005c\n \t(defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2))\n \n@@ -313,9 +318,6 @@ solo:\n \tlwsl_info(\u0022%s: %s: lookup %s:%u\u005cn\u0022, __func__, wsi-\u003elc.gutag, ads, port);\n \twsi-\u003econn_port \u003d (uint16_t)port;\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\twsi-\u003edetlat.earliest_write_req_pre_write \u003d lws_now_usecs();\n-#endif\n #if !defined(LWS_WITH_SYS_ASYNC_DNS)\n \tn \u003d 0;\n \tif (!wsi-\u003edns_sorted_list.count) {\ndiff --git a/lib/core-net/client/connect3.c b/lib/core-net/client/connect3.c\nindex 5418cfd..ec1b250 100644\n--- a/lib/core-net/client/connect3.c\n+++ b/lib/core-net/client/connect3.c\n@@ -203,6 +203,7 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads,\n \t\tdefault:\n \t\t\tlwsl_debug(\u0022%s: getsockopt check: conn fail: errno %d\u005cn\u0022,\n \t\t\t\t\t__func__, LWS_ERRNO);\n+\t\t\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_NOGO);\n \t\t\tgoto try_next_dns_result_fds;\n \t\t}\n \t}\n@@ -236,19 +237,6 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads,\n \t}\n #endif\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tif (lwsi_state(wsi) \u003d\u003d LRS_WAITING_DNS \u0026\u0026\n-\t wsi-\u003ea.context-\u003edetailed_latency_cb) {\n-\t\twsi-\u003edetlat.type \u003d LDLT_NAME_RESOLUTION;\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] \u003d\n-\t\t\t(uint32_t)(lws_now_usecs() -\n-\t\t\twsi-\u003edetlat.earliest_write_req_pre_write);\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d 0;\n-\t\tlws_det_lat_cb(wsi-\u003ea.context, \u0026wsi-\u003edetlat);\n-\t\twsi-\u003edetlat.earliest_write_req_pre_write \u003d lws_now_usecs();\n-\t}\n-#endif\n-\n \t/*\n \t * Let's try directly connecting to each of the results in turn until\n \t * one works, or we run out of results...\n@@ -393,11 +381,6 @@ ads_known:\n \t * The actual connection attempt\n \t */\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\twsi-\u003edetlat.earliest_write_req \u003d\n-\t\twsi-\u003edetlat.earliest_write_req_pre_write \u003d lws_now_usecs();\n-#endif\n-\n #if defined(LWS_ESP_PLATFORM)\n \terrno \u003d 0;\n #endif\n@@ -412,6 +395,12 @@ ads_known:\n \t * Finally, make the actual connection attempt\n \t */\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tif (wsi-\u003ecal_conn.mt)\n+\t\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_NOGO);\n+\tlws_metrics_caliper_bind(wsi-\u003ecal_conn, wsi-\u003ea.context-\u003emt_conn_tcp);\n+#endif\n+\n \tm \u003d connect(wsi-\u003edesc.sockfd, (const struct sockaddr *)psa, (unsigned int)n);\n \tif (m \u003d\u003d -1) {\n \t\t/*\n@@ -438,6 +427,8 @@ ads_known:\n \t\t\t * The connect() failed immediately...\n \t\t\t */\n \n+\t\t\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_NOGO);\n+\n #if defined(_DEBUG)\n #if defined(LWS_WITH_UNIX_SOCK)\n \t\t\tif (!wsi-\u003eunix_skt) {\n@@ -503,7 +494,12 @@ conn_good:\n \t\t\t\t\u0026salen) \u003d\u003d -1)\n \t\t\tlwsl_warn(\u0022getsockname: %s\u005cn\u0022, strerror(LWS_ERRNO));\n #if defined(_DEBUG)\n-\t\tlws_sa46_write_numeric_address(\u0026wsi-\u003esa46_local, buf, sizeof(buf));\n+#if defined(LWS_WITH_UNIX_SOCK)\n+\t\tif (wsi-\u003eunix_skt)\n+\t\t\tbuf[0] \u003d '\u005c0';\n+\t\telse\n+#endif\n+\t\t\tlws_sa46_write_numeric_address(\u0026wsi-\u003esa46_local, buf, sizeof(buf));\n \n \t\tlwsl_info(\u0022%s: %s: source ads %s\u005cn\u0022, __func__, wsi-\u003elc.gutag, buf);\n #endif\n@@ -511,20 +507,7 @@ conn_good:\n #endif\n \n \tlws_sul_cancel(\u0026wsi-\u003esul_connect_timeout);\n-\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tif (wsi-\u003ea.context-\u003edetailed_latency_cb) {\n-\t\twsi-\u003edetlat.type \u003d LDLT_CONNECTION;\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] \u003d\n-\t\t\t(uint32_t)(lws_now_usecs() -\n-\t\t\twsi-\u003edetlat.earliest_write_req_pre_write);\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d 0;\n-\t\tlws_det_lat_cb(wsi-\u003ea.context, \u0026wsi-\u003edetlat);\n-\t\twsi-\u003edetlat.earliest_write_req \u003d\n-\t\t\twsi-\u003edetlat.earliest_write_req_pre_write \u003d\n-\t\t\t\t\t\t\tlws_now_usecs();\n-\t}\n-#endif\n+\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_GO);\n \n \tlws_addrinfo_clean(wsi);\n \n@@ -550,6 +533,8 @@ oom4:\n \t\t/* do the full wsi close flow */\n \t\tgoto failed1;\n \n+\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_NOGO);\n+\n \t/*\n \t * We can't be an active client connection any more, if we thought\n \t * that was what we were going to be doing. It should be if we are\ndiff --git a/lib/core-net/client/connect4.c b/lib/core-net/client/connect4.c\nindex eaa05e2..8961120 100644\n--- a/lib/core-net/client/connect4.c\n+++ b/lib/core-net/client/connect4.c\n@@ -156,11 +156,7 @@ send_hs:\n \t\t * wait in the queue until it's possible to send them.\n \t\t */\n \t\tlws_callback_on_writable(wsi_piggyback);\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\t\twsi-\u003edetlat.earliest_write_req \u003d\n-\t\t\t\twsi-\u003edetlat.earliest_write_req_pre_write \u003d\n-\t\t\t\t\t\t\t\tlws_now_usecs();\n-#endif\n+\n \t\tlwsl_info(\u0022%s: %s: waiting to send hdrs (par state 0x%x)\u005cn\u0022,\n \t\t\t __func__, wsi-\u003elc.gutag, lwsi_state(wsi_piggyback));\n \t} else {\ndiff --git a/lib/core-net/close.c b/lib/core-net/close.c\nindex fe01dff..806d772 100644\n--- a/lib/core-net/close.c\n+++ b/lib/core-net/close.c\n@@ -238,8 +238,6 @@ lws_inform_client_conn_fail(struct lws *wsi, void *arg, size_t len)\n \t\treturn;\n \n \twsi-\u003ealready_did_cce \u003d 1;\n-\tlws_stats_bump(\u0026wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi],\n-\t\t LWSSTATS_C_CONNS_CLIENT_FAILED, 1);\n \n \tif (!wsi-\u003ea.protocol)\n \t\treturn;\n@@ -293,6 +291,20 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason,\n \tcontext \u003d wsi-\u003ea.context;\n \tpt \u003d \u0026context-\u003ept[(int)wsi-\u003etsi];\n \n+#if defined(LWS_WITH_SYS_METRICS) \u0026\u0026 \u005c\n+ (defined(LWS_WITH_CLIENT) || defined(LWS_WITH_SERVER))\n+\t/* wsi level: only reports if dangling caliper */\n+\tif (wsi-\u003ecal_conn.mt \u0026\u0026 wsi-\u003ecal_conn.us_start) {\n+\t\tif ((lws_metrics_priv_to_pub(wsi-\u003ecal_conn.mt)-\u003eflags) \u0026 LWSMTFL_REPORT_HIST) {\n+\t\t\tlws_metrics_caliper_report_hist(wsi-\u003ecal_conn, (struct lws *)NULL);\n+\t\t} else {\n+\t\t\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_NOGO);\n+\t\t\tlws_metrics_caliper_done(wsi-\u003ecal_conn);\n+\t\t}\n+\t} else\n+\t\tlws_metrics_caliper_done(wsi-\u003ecal_conn);\n+#endif\n+\n #if defined(LWS_WITH_SYS_ASYNC_DNS)\n \tif (wsi \u003d\u003d context-\u003easync_dns.wsi)\n \t\tcontext-\u003easync_dns.wsi \u003d NULL;\n@@ -300,8 +312,6 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason,\n \n \tlws_pt_assert_lock_held(pt);\n \n-\tlws_stats_bump(pt, LWSSTATS_C_API_CLOSE, 1);\n-\n #if defined(LWS_WITH_CLIENT)\n \n \tlws_free_set_NULL(wsi-\u003ecli_hostname_copy);\n@@ -719,6 +729,14 @@ async_close:\n \t\t\t\tlws_sspc_handle_t *h \u003d (lws_sspc_handle_t *)wsi-\u003ea.opaque_user_data;\n \n \t\t\t\tif (h) { // \u0026\u0026 (h-\u003einfo.flags \u0026 LWSSSINFLAGS_ACCEPTED)) {\n+\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t\t\t\t/*\n+\t\t\t\t\t * If any hanging caliper measurement, dump it, and free any tags\n+\t\t\t\t\t */\n+\t\t\t\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n+\n \t\t\t\t\th-\u003ecwsi \u003d NULL;\n \t\t\t\t\t//wsi-\u003ea.opaque_user_data \u003d NULL;\n \t\t\t\t}\n@@ -729,6 +747,12 @@ async_close:\n \n \t\t\tif (h) { // \u0026\u0026 (h-\u003einfo.flags \u0026 LWSSSINFLAGS_ACCEPTED)) {\n \n+\t\t\t\t/*\n+\t\t\t\t * ss level: only reports if dangling caliper\n+\t\t\t\t * not already reported\n+\t\t\t\t */\n+\t\t\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, wsi);\n+\n \t\t\t\th-\u003ewsi \u003d NULL;\n \t\t\t\twsi-\u003ea.opaque_user_data \u003d NULL;\n \ndiff --git a/lib/core-net/detailed-latency.c b/lib/core-net/detailed-latency.c\ndeleted file mode 100644\nindex a8c1d09..0000000\n--- a/lib/core-net/detailed-latency.c\n+++ /dev/null\n@@ -1,79 +0,0 @@\n-/*\n- * libwebsockets - small server side websockets and web server implementation\n- *\n- * Copyright (C) 2010 - 2019 Andy Green \u003candy@warmcat.com\u003e\n- *\n- * Permission is hereby granted, free of charge, to any person obtaining a copy\n- * of this software and associated documentation files (the \u0022Software\u0022), to\n- * deal in the Software without restriction, including without limitation the\n- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n- * sell copies of the Software, and to permit persons to whom the Software is\n- * furnished to do so, subject to the following conditions:\n- *\n- * The above copyright notice and this permission notice shall be included in\n- * all copies or substantial portions of the Software.\n- *\n- * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n- * IN THE SOFTWARE.\n- */\n-\n-#include \u0022private-lib-core.h\u0022\n-\n-int\n-lws_det_lat_active(struct lws_context *context)\n-{\n-\treturn !!context-\u003edetailed_latency_cb;\n-}\n-\n-int\n-lws_det_lat_cb(struct lws_context *context, lws_detlat_t *d)\n-{\n-\tint n;\n-\n-\tif (!context-\u003edetailed_latency_cb)\n-\t\treturn 0;\n-\n-\tn \u003d context-\u003edetailed_latency_cb(context, d);\n-\n-\tmemset(\u0026d-\u003elatencies, 0, sizeof(d-\u003elatencies));\n-\n-\treturn n;\n-}\n-\n-static const char types[] \u003d \u0022rwNCTt????\u0022;\n-int\n-lws_det_lat_plot_cb(struct lws_context *context, const lws_detlat_t *d)\n-{\n-\tchar buf[80], *p \u003d buf, *end \u003d \u0026p[sizeof(buf) - 1];\n-\n-\tif (!context-\u003edetailed_latency_filepath)\n-\t\treturn 1;\n-\n-\tif (context-\u003elatencies_fd \u003d\u003d -1) {\n-\t\tcontext-\u003elatencies_fd \u003d open(context-\u003edetailed_latency_filepath,\n-\t\t\t\tLWS_O_CREAT | LWS_O_TRUNC | LWS_O_WRONLY, 0600);\n-\t\tif (context-\u003elatencies_fd \u003d\u003d -1)\n-\t\t\treturn 1;\n-\t}\n-\n-\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p),\n-\t\t\t \u0022%llu %c %u %u %u %u %u %zu %zu\u005cn\u0022,\n-\t\t\t (unsigned long long)lws_now_usecs(), types[d-\u003etype],\n-\t\t\t d-\u003elatencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE],\n-\t\t\t d-\u003elatencies[LAT_DUR_PROXY_CLIENT_WRITE_TO_PROXY_RX],\n-\t\t\t d-\u003elatencies[LAT_DUR_PROXY_PROXY_REQ_TO_WRITE],\n-\t\t\t d-\u003elatencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] +\n-\t\t\t d-\u003elatencies[LAT_DUR_PROXY_CLIENT_WRITE_TO_PROXY_RX] +\n-\t\t\t d-\u003elatencies[LAT_DUR_PROXY_PROXY_REQ_TO_WRITE],\n-\t\t\t d-\u003elatencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX],\n-\t\t\t d-\u003eacc_size, d-\u003ereq_size);\n-\n-\twrite(context-\u003elatencies_fd, buf, lws_ptr_diff_size_t(p, buf));\n-\n-\treturn 0;\n-}\ndiff --git a/lib/core-net/network.c b/lib/core-net/network.c\nindex 72d5db9..866ff3c 100644\n--- a/lib/core-net/network.c\n+++ b/lib/core-net/network.c\n@@ -890,7 +890,7 @@ lws_sa46_write_numeric_address(lws_sockaddr46 *sa46, char *buf, size_t len)\n \t\treturn lws_snprintf(buf, len, \u0022(unset)\u0022);\n \n \tif (sa46-\u003esa4.sin_family \u003d\u003d AF_INET6)\n-\t\tlws_snprintf(buf, len, \u0022(ipv6 unsupp)\u0022);\n+\t\treturn lws_snprintf(buf, len, \u0022(ipv6 unsupp)\u0022);\n \n \tlws_snprintf(buf, len, \u0022(AF%d unsupp)\u0022, (int)sa46-\u003esa4.sin_family);\n \ndiff --git a/lib/core-net/output.c b/lib/core-net/output.c\nindex 71e5912..8a2fd90 100644\n--- a/lib/core-net/output.c\n+++ b/lib/core-net/output.c\n@@ -31,7 +31,6 @@ int\n lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)\n {\n \tstruct lws_context *context \u003d lws_get_context(wsi);\n-\tstruct lws_context_per_thread *pt \u003d \u0026wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi];\n \tsize_t real_len \u003d len;\n \tunsigned int n, m;\n \n@@ -59,8 +58,6 @@ lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)\n \t\t\t (unsigned long)len);\n \t}\n \n-\tlws_stats_bump(pt, LWSSTATS_C_API_WRITE, 1);\n-\n \t/* just ignore sends after we cleared the truncation buffer */\n \tif (lwsi_state(wsi) \u003d\u003d LRS_FLUSHING_BEFORE_CLOSE \u0026\u0026\n \t !lws_has_buffered_out(wsi)\n@@ -215,9 +212,6 @@ lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)\n \t\t\t\t real_len - m) \u003c 0)\n \t\treturn -1;\n \n-\tlws_stats_bump(pt, LWSSTATS_C_WRITE_PARTIALS, 1);\n-\tlws_stats_bump(pt, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, m);\n-\n #if defined(LWS_WITH_UDP)\n \tif (lws_wsi_is_udp(wsi))\n \t\t/* stash original destination for fulfilling UDP partials */\n@@ -234,57 +228,30 @@ int\n lws_write(struct lws *wsi, unsigned char *buf, size_t len,\n \t enum lws_write_protocol wp)\n {\n-\tstruct lws_context_per_thread *pt \u003d \u0026wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi];\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tlws_usec_t us;\n-#endif\n \tint m;\n \n-\tlws_stats_bump(pt, LWSSTATS_C_API_LWS_WRITE, 1);\n-\n \tif ((int)len \u003c 0) {\n \t\tlwsl_err(\u0022%s: suspicious len int %d, ulong %lu\u005cn\u0022, __func__,\n \t\t\t\t(int)len, (unsigned long)len);\n \t\treturn -1;\n \t}\n \n-\tlws_stats_bump(pt, LWSSTATS_B_WRITE, len);\n-\n #ifdef LWS_WITH_ACCESS_LOG\n \twsi-\u003ehttp.access_log.sent +\u003d len;\n #endif\n-#if defined(LWS_WITH_SERVER_STATUS)\n-\tif (wsi-\u003ea.vhost)\n-\t\twsi-\u003ea.vhost-\u003econn_stats.tx +\u003d len;\n-#endif\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tus \u003d lws_now_usecs();\n-#endif\n \n \tassert(wsi-\u003erole_ops);\n \n \tif (!lws_rops_fidx(wsi-\u003erole_ops, LWS_ROPS_write_role_protocol))\n-\t\treturn lws_issue_raw(wsi, buf, len);\n-\n-\tm \u003d lws_rops_func_fidx(wsi-\u003erole_ops, LWS_ROPS_write_role_protocol).\n-\t\t\twrite_role_protocol(wsi, buf, len, \u0026wp);\n-\tif (m \u003c 0)\n-\t\treturn m;\n-\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tif (wsi-\u003ea.context-\u003edetailed_latency_cb) {\n-\t\twsi-\u003edetlat.req_size \u003d len;\n-\t\twsi-\u003edetlat.acc_size \u003d (unsigned int)m;\n-\t\twsi-\u003edetlat.type \u003d LDLT_WRITE;\n-\t\tif (wsi-\u003edetlat.earliest_write_req_pre_write)\n-\t\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_PROXY_REQ_TO_WRITE] \u003d\n-\t\t\t\t\t(uint32_t)(us - wsi-\u003edetlat.earliest_write_req_pre_write);\n-\t\telse\n-\t\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_PROXY_REQ_TO_WRITE] \u003d 0;\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d (uint32_t)(lws_now_usecs() - us);\n-\t\tlws_det_lat_cb(wsi-\u003ea.context, \u0026wsi-\u003edetlat);\n+\t\tm \u003d lws_issue_raw(wsi, buf, len);\n+\telse\n+\t\tm \u003d lws_rops_func_fidx(wsi-\u003erole_ops, LWS_ROPS_write_role_protocol).\n+\t\t\t\twrite_role_protocol(wsi, buf, len, \u0026wp);\n \n-\t}\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tif (wsi-\u003ea.vhost)\n+\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_tx, (char)\n+\t\t\t\t (m \u003c 0 ? METRES_NOGO : METRES_GO), len);\n #endif\n \n \treturn m;\n@@ -316,19 +283,20 @@ lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, size_t len)\n \ten \u003d LWS_ERRNO;\n \tif (n \u003e\u003d 0) {\n \n-\t\tif (!n \u0026\u0026 wsi-\u003eunix_skt)\n-\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t//if (!n \u0026\u0026 wsi-\u003eunix_skt)\n+\t\t//\tgoto do_err;\n \n \t\t/*\n \t\t * See https://libwebsockets.org/\n \t\t * pipermail/libwebsockets/2019-March/007857.html\n \t\t */\n-\t\tif (!n)\n-\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\tif (!n \u0026\u0026 !wsi-\u003eunix_skt)\n+\t\t\tgoto do_err;\n \n-#if defined(LWS_WITH_SERVER_STATUS)\n+#if defined(LWS_WITH_SYS_METRICS) \u0026\u0026 defined(LWS_WITH_SERVER)\n \t\tif (wsi-\u003ea.vhost)\n-\t\t\twsi-\u003ea.vhost-\u003econn_stats.rx \u003d (unsigned long long)(wsi-\u003ea.vhost-\u003econn_stats.rx + (unsigned long long)(long long)n);\n+\t\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_rx,\n+\t\t\t\t\t METRES_GO /* rx */, (unsigned int)n);\n #endif\n \n \t\treturn n;\n@@ -339,7 +307,14 @@ lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, size_t len)\n \t en \u003d\u003d LWS_EINTR)\n \t\treturn LWS_SSL_CAPABLE_MORE_SERVICE;\n \n-\tlwsl_info(\u0022error on reading from skt : %d\u005cn\u0022, en);\n+do_err:\n+#if defined(LWS_WITH_SYS_METRICS) \u0026\u0026 defined(LWS_WITH_SERVER)\n+\tif (wsi-\u003ea.vhost)\n+\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_rx, METRES_NOGO, 0u);\n+#endif\n+\n+\tlwsl_info(\u0022%s: error on reading from skt : %d, errno %d\u005cn\u0022,\n+\t\t\t__func__, n, en);\n \n \treturn LWS_SSL_CAPABLE_ERROR;\n }\ndiff --git a/lib/core-net/pollfd.c b/lib/core-net/pollfd.c\nindex f3ecb61..a59f866 100644\n--- a/lib/core-net/pollfd.c\n+++ b/lib/core-net/pollfd.c\n@@ -515,7 +515,6 @@ lws_change_pollfd(struct lws *wsi, int _and, int _or)\n int\n lws_callback_on_writable(struct lws *wsi)\n {\n-\tstruct lws_context_per_thread *pt;\n \tstruct lws *w \u003d wsi;\n \n \tif (lwsi_state(wsi) \u003d\u003d LRS_SHUTDOWN)\n@@ -524,21 +523,6 @@ lws_callback_on_writable(struct lws *wsi)\n \tif (wsi-\u003esocket_is_permanently_unusable)\n \t\treturn 0;\n \n-\tpt \u003d \u0026wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi];\n-\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tif (!wsi-\u003edetlat.earliest_write_req)\n-\t\twsi-\u003edetlat.earliest_write_req \u003d lws_now_usecs();\n-#endif\n-\n-\tlws_stats_bump(pt, LWSSTATS_C_WRITEABLE_CB_REQ, 1);\n-#if defined(LWS_WITH_STATS)\n-\tif (!wsi-\u003eactive_writable_req_us) {\n-\t\twsi-\u003eactive_writable_req_us \u003d lws_now_usecs();\n-\t\tlws_stats_bump(pt, LWSSTATS_C_WRITEABLE_CB_EFF_REQ, 1);\n-\t}\n-#endif\n-\n \tif (lws_rops_fidx(wsi-\u003erole_ops, LWS_ROPS_callback_on_writable)) {\n \t\tint q \u003d lws_rops_func_fidx(wsi-\u003erole_ops,\n \t\t\t\t\t LWS_ROPS_callback_on_writable).\ndiff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h\nindex a489fc4..86094fb 100644\n--- a/lib/core-net/private-lib-core-net.h\n+++ b/lib/core-net/private-lib-core-net.h\n@@ -265,41 +265,6 @@ struct lws_timed_vh_protocol {\n #endif\n \n /*\n- * lws_dsh\n-*/\n-\n-typedef struct lws_dsh_obj_head {\n-\tlws_dll2_owner_t\t\towner;\n-\tsize_t\t\t\t\ttotal_size; /* for this kind in dsh */\n-\tint\t\t\t\tkind;\n-} lws_dsh_obj_head_t;\n-\n-typedef struct lws_dsh_obj {\n-\tlws_dll2_t\t\t\tlist;\t/* must be first */\n-\tstruct lws_dsh\t \t\t*dsh;\t/* invalid when on free list */\n-\tsize_t\t\t\t\tsize;\t/* invalid when on free list */\n-\tsize_t\t\t\t\tasize;\n-\tint\t\t\t\tkind; /* so we can account at free */\n-} lws_dsh_obj_t;\n-\n-typedef struct lws_dsh {\n-\tlws_dll2_t\t\t\tlist;\n-\tuint8_t\t\t\t\t*buf;\n-\tlws_dsh_obj_head_t\t\t*oha;\t/* array of object heads/kind */\n-\tsize_t\t\t\t\tbuffer_size;\n-\tsize_t\t\t\t\tlocally_in_use;\n-\tsize_t\t\t\t\tlocally_free;\n-\tint\t\t\t\tcount_kinds;\n-\tuint8_t\t\t\t\tbeing_destroyed;\n-\t/*\n-\t * Overallocations at create:\n-\t *\n-\t * - the buffer itself\n-\t * - the object heads array\n-\t */\n-} lws_dsh_t;\n-\n-/*\n * lws_async_dns\n */\n \n@@ -371,11 +336,6 @@ struct lws_context_per_thread {\n #if defined(LWS_ROLE_CGI)\n \tlws_sorted_usec_list_t sul_cgi;\n #endif\n-#if defined(LWS_WITH_STATS)\n-\tuint64_t lws_stats[LWSSTATS_SIZE];\n-\tint updated;\n-\tlws_sorted_usec_list_t sul_stats;\n-#endif\n #if defined(LWS_WITH_PEER_LIMITS)\n \tlws_sorted_usec_list_t sul_peer_limits;\n #endif\n@@ -419,10 +379,6 @@ struct lws_context_per_thread {\n \tvoid\t\t*evlib_pt; /* overallocated */\n #endif\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tlws_usec_t\tust_left_poll;\n-#endif\n-\n \t/* --- */\n \n \tunsigned long count_conns;\n@@ -454,14 +410,6 @@ struct lws_context_per_thread {\n \tunsigned char is_destroyed:1;\n };\n \n-#if defined(LWS_WITH_SERVER_STATUS)\n-struct lws_conn_stats {\n-\tunsigned long long rx, tx;\n-\tunsigned long h1_conn, h1_trans, h2_trans, ws_upg, h2_alpn, h2_subs,\n-\t\t h2_upg, rejected, mqtt_subs;\n-};\n-#endif\n-\n /*\n * virtual host -related context information\n * vhostwide SSL context\n@@ -510,8 +458,9 @@ struct lws_vhost {\n #if defined(LWS_WITH_EVENT_LIBS)\n \tvoid\t\t*evlib_vh; /* overallocated */\n #endif\n-#if defined(LWS_WITH_SERVER_STATUS)\n-\tstruct lws_conn_stats conn_stats;\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_metric_t\t*mt_traffic_rx;\n+\tlws_metric_t\t*mt_traffic_tx;\n #endif\n \n #if defined(LWS_WITH_SYS_FAULT_INJECTION)\n@@ -707,10 +656,6 @@ struct lws {\n \tvoid\t\t\t\t*evlib_wsi; /* overallocated */\n #endif\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tlws_detlat_t\tdetlat;\n-#endif\n-\n \tlws_sorted_usec_list_t\t\tsul_timeout;\n \tlws_sorted_usec_list_t\t\tsul_hrtimer;\n \tlws_sorted_usec_list_t\t\tsul_validity;\n@@ -728,6 +673,8 @@ struct lws {\n \tstruct lws_dll2\t\t\tdll2_cli_txn_queue;\n \tstruct lws_dll2_owner\t\tdll2_cli_txn_queue_owner;\n \n+\t/**\u003c caliper is reused for tcp, tls and txn conn phases */\n+\n \tlws_dll2_t\t\t\tspeculative_list;\n \tlws_dll2_owner_t\t\tspeculative_connect_owner;\n \t/* wsis: additional connection candidates */\n@@ -741,6 +688,10 @@ struct lws {\n \t/**\u003c Fault Injection ctx for the wsi, hierarchy wsi-\u003evhost-\u003econtext */\n #endif\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_metrics_caliper_compose(cal_conn)\n+#endif\n+\n \tlws_sockaddr46\t\t\tsa46_local;\n \tlws_sockaddr46\t\t\tsa46_peer;\n \n@@ -779,12 +730,7 @@ struct lws {\n #endif\n \n \tlws_sock_file_fd_type\t\tdesc; /* .filefd / .sockfd */\n-#if defined(LWS_WITH_STATS)\n-\tuint64_t active_writable_req_us;\n-#if defined(LWS_WITH_TLS)\n-\tuint64_t accept_start_us;\n-#endif\n-#endif\n+\n \tlws_wsi_state_t\t\t\twsistate;\n \tlws_wsi_state_t\t\t\twsistate_pre_close;\n \n@@ -909,9 +855,6 @@ struct lws {\n #if defined(LWS_WITH_CGI) || defined(LWS_WITH_CLIENT)\n \tchar reason_bf; /* internal writeable callback reason bitfield */\n #endif\n-#if defined(LWS_WITH_STATS) \u0026\u0026 defined(LWS_WITH_TLS)\n-\tchar seen_rx;\n-#endif\n #if defined(LWS_WITH_NETLINK)\n \tlws_route_uidx_t\t\tpeer_route_uidx;\n \t/**\u003c unique index of the route the connection is estimated to take */\n@@ -1224,11 +1167,6 @@ lws_destroy_event_pipe(struct lws *wsi);\n int\n lws_socks5c_generate_msg(struct lws *wsi, enum socks_msg_type type, ssize_t *msg_len);\n \n-#if defined(LWS_WITH_SERVER_STATUS)\n-void\n-lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs);\n-#endif\n-\n #if defined(LWS_WITH_DEPRECATED_THINGS)\n int\n __lws_timed_callback_remove(struct lws_vhost *vh, struct lws_timed_vh_protocol *p);\n@@ -1433,21 +1371,6 @@ lws_sort_dns(struct lws *wsi, const struct addrinfo *result);\n int\n lws_broadcast(struct lws_context_per_thread *pt, int reason, void *in, size_t len);\n \n-#if defined(LWS_WITH_STATS)\n- void\n- lws_stats_bump(struct lws_context_per_thread *pt, int i, uint64_t bump);\n- void\n- lws_stats_max(struct lws_context_per_thread *pt, int index, uint64_t val);\n-#else\n- static LWS_INLINE uint64_t lws_stats_bump(\n-\t\tstruct lws_context_per_thread *pt, int index, uint64_t bump) {\n-\t(void)pt; (void)index; (void)bump; return 0; }\n- static LWS_INLINE uint64_t lws_stats_max(\n-\t\tstruct lws_context_per_thread *pt, int index, uint64_t val) {\n-\t(void)pt; (void)index; (void)val; return 0; }\n-#endif\n-\n-\n \n #if defined(LWS_WITH_PEER_LIMITS)\n void\n@@ -1501,6 +1424,9 @@ void\n __lws_reset_wsi(struct lws *wsi);\n \n void\n+lws_metrics_dump(struct lws_context *ctx);\n+\n+void\n lws_inform_client_conn_fail(struct lws *wsi, void *arg, size_t len);\n \n #if defined(LWS_WITH_SYS_ASYNC_DNS)\ndiff --git a/lib/core-net/server.c b/lib/core-net/server.c\ndeleted file mode 100644\nindex dff3348..0000000\n--- a/lib/core-net/server.c\n+++ /dev/null\n@@ -1,326 +0,0 @@\n-/*\n- * libwebsockets - small server side websockets and web server implementation\n- *\n- * Copyright (C) 2010 - 2019 Andy Green \u003candy@warmcat.com\u003e\n- *\n- * Permission is hereby granted, free of charge, to any person obtaining a copy\n- * of this software and associated documentation files (the \u0022Software\u0022), to\n- * deal in the Software without restriction, including without limitation the\n- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n- * sell copies of the Software, and to permit persons to whom the Software is\n- * furnished to do so, subject to the following conditions:\n- *\n- * The above copyright notice and this permission notice shall be included in\n- * all copies or substantial portions of the Software.\n- *\n- * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n- * IN THE SOFTWARE.\n- */\n-\n-#include \u0022private-lib-core.h\u0022\n-\n-#if defined(LWS_WITH_SERVER_STATUS)\n-\n-void\n-lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs)\n-{\n-\tconst struct lws_vhost *vh \u003d ctx-\u003evhost_list;\n-\n-\twhile (vh) {\n-\n-\t\tcs-\u003erx +\u003d vh-\u003econn_stats.rx;\n-\t\tcs-\u003etx +\u003d vh-\u003econn_stats.tx;\n-\t\tcs-\u003eh1_conn +\u003d vh-\u003econn_stats.h1_conn;\n-\t\tcs-\u003eh1_trans +\u003d vh-\u003econn_stats.h1_trans;\n-\t\tcs-\u003eh2_trans +\u003d vh-\u003econn_stats.h2_trans;\n-\t\tcs-\u003ews_upg +\u003d vh-\u003econn_stats.ws_upg;\n-\t\tcs-\u003eh2_upg +\u003d vh-\u003econn_stats.h2_upg;\n-\t\tcs-\u003eh2_alpn +\u003d vh-\u003econn_stats.h2_alpn;\n-\t\tcs-\u003eh2_subs +\u003d vh-\u003econn_stats.h2_subs;\n-\t\tcs-\u003erejected +\u003d vh-\u003econn_stats.rejected;\n-\n-\t\tvh \u003d vh-\u003evhost_next;\n-\t}\n-}\n-\n-int\n-lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len)\n-{\n-#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)\n-\tstatic const char * const prots[] \u003d {\n-\t\t\u0022http://\u0022,\n-\t\t\u0022https://\u0022,\n-\t\t\u0022file://\u0022,\n-\t\t\u0022cgi://\u0022,\n-\t\t\u0022\u003ehttp://\u0022,\n-\t\t\u0022\u003ehttps://\u0022,\n-\t\t\u0022callback://\u0022\n-\t};\n-#endif\n-\tchar *orig \u003d buf, *end \u003d buf + len - 1, first;\n-\tint n;\n-\n-\tif (len \u003c 100)\n-\t\treturn 0;\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n-\t\t\t\u0022{\u005cn \u005c\u0022name\u005c\u0022:\u005c\u0022%s\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022port\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022use_ssl\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022sts\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022rx\u005c\u0022:\u005c\u0022%llu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022tx\u005c\u0022:\u005c\u0022%llu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h1_conn\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h1_trans\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h2_trans\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022ws_upg\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022rejected\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h2_upg\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h2_alpn\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h2_subs\u005c\u0022:\u005c\u0022%lu\u005c\u0022\u0022\n-\t\t\t,\n-\t\t\tvh-\u003ename, vh-\u003elisten_port,\n-#if defined(LWS_WITH_TLS)\n-\t\t\tvh-\u003etls.use_ssl \u0026 LCCSCF_USE_SSL,\n-#else\n-\t\t\t0,\n-#endif\n-\t\t\t!!(vh-\u003eoptions \u0026 LWS_SERVER_OPTION_STS),\n-\t\t\tvh-\u003econn_stats.rx, vh-\u003econn_stats.tx,\n-\t\t\tvh-\u003econn_stats.h1_conn,\n-\t\t\tvh-\u003econn_stats.h1_trans,\n-\t\t\tvh-\u003econn_stats.h2_trans,\n-\t\t\tvh-\u003econn_stats.ws_upg,\n-\t\t\tvh-\u003econn_stats.rejected,\n-\t\t\tvh-\u003econn_stats.h2_upg,\n-\t\t\tvh-\u003econn_stats.h2_alpn,\n-\t\t\tvh-\u003econn_stats.h2_subs\n-\t);\n-#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)\n-\tif (vh-\u003ehttp.mount_list) {\n-\t\tconst struct lws_http_mount *m \u003d vh-\u003ehttp.mount_list;\n-\n-\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022,\u005cn \u005c\u0022mounts\u005c\u0022:[\u0022);\n-\t\tfirst \u003d 1;\n-\t\twhile (m) {\n-\t\t\tif (!first)\n-\t\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022,\u0022);\n-\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n-\t\t\t\t\t\u0022\u005cn {\u005cn \u005c\u0022mountpoint\u005c\u0022:\u005c\u0022%s\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\t\u0022 \u005c\u0022origin\u005c\u0022:\u005c\u0022%s%s\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\t\u0022 \u005c\u0022cache_max_age\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\t\u0022 \u005c\u0022cache_reuse\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\t\u0022 \u005c\u0022cache_revalidate\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\t\u0022 \u005c\u0022cache_intermediaries\u005c\u0022:\u005c\u0022%d\u005c\u0022\u005cn\u0022\n-\t\t\t\t\t,\n-\t\t\t\t\tm-\u003emountpoint,\n-\t\t\t\t\tprots[m-\u003eorigin_protocol],\n-\t\t\t\t\tm-\u003eorigin,\n-\t\t\t\t\tm-\u003ecache_max_age,\n-\t\t\t\t\tm-\u003ecache_reusable,\n-\t\t\t\t\tm-\u003ecache_revalidate,\n-\t\t\t\t\tm-\u003ecache_intermediaries);\n-\t\t\tif (m-\u003edef)\n-\t\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n-\t\t\t\t\t\t\u0022,\u005cn \u005c\u0022default\u005c\u0022:\u005c\u0022%s\u005c\u0022\u0022,\n-\t\t\t\t\t\tm-\u003edef);\n-\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022\u005cn }\u0022);\n-\t\t\tfirst \u003d 0;\n-\t\t\tm \u003d m-\u003emount_next;\n-\t\t}\n-\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022\u005cn ]\u0022);\n-\t}\n-#endif\n-\tif (vh-\u003eprotocols) {\n-\t\tn \u003d 0;\n-\t\tfirst \u003d 1;\n-\n-\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022,\u005cn \u005c\u0022ws-protocols\u005c\u0022:[\u0022);\n-\t\twhile (n \u003c vh-\u003ecount_protocols) {\n-\t\t\tif (!first)\n-\t\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022,\u0022);\n-\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n-\t\t\t\t\t\u0022\u005cn {\u005cn \u005c\u0022%s\u005c\u0022:{\u005cn\u0022\n-\t\t\t\t\t\u0022 \u005c\u0022status\u005c\u0022:\u005c\u0022ok\u005c\u0022\u005cn }\u005cn }\u0022\n-\t\t\t\t\t,\n-\t\t\t\t\tvh-\u003eprotocols[n].name);\n-\t\t\tfirst \u003d 0;\n-\t\t\tn++;\n-\t\t}\n-\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022\u005cn ]\u0022);\n-\t}\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022\u005cn}\u0022);\n-\n-\treturn lws_ptr_diff(buf, orig);\n-}\n-\n-\n-int\n-lws_json_dump_context(const struct lws_context *context, char *buf, int len,\n-\t\tint hide_vhosts)\n-{\n-\tchar *orig \u003d buf, *end \u003d buf + len - 1, first \u003d 1;\n-\tconst struct lws_vhost *vh \u003d context-\u003evhost_list;\n-\tconst struct lws_context_per_thread *pt;\n-\tint n, listening \u003d 0, cgi_count \u003d 0, fd;\n-\tstruct lws_conn_stats cs;\n-\tdouble d \u003d 0;\n-#ifdef LWS_WITH_CGI\n-\tstruct lws_cgi * const *pcgi;\n-#endif\n-\n-//#ifdef LWS_WITH_LIBUV \u0026\u0026\n-//\tuv_uptime(\u0026d);\n-//#endif\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022{ \u0022\n-\t\t\t \u0022\u005c\u0022version\u005c\u0022:\u005c\u0022%s\u005c\u0022,\u005cn\u0022\n-\t\t\t \u0022\u005c\u0022uptime\u005c\u0022:\u005c\u0022%ld\u005c\u0022,\u005cn\u0022,\n-\t\t\t lws_get_library_version(),\n-\t\t\t (long)d);\n-\n-#ifdef LWS_HAVE_GETLOADAVG\n-#if defined(__sun)\n-#include \u003csys/loadavg.h\u003e\n-#endif\n-\t{\n-\t\tdouble d[3];\n-\t\tint m;\n-\n-\t\tm \u003d getloadavg(d, 3);\n-\t\tfor (n \u003d 0; n \u003c m; n++) {\n-\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n-\t\t\t\t\u0022\u005c\u0022l%d\u005c\u0022:\u005c\u0022%.2f\u005c\u0022,\u005cn\u0022,\n-\t\t\t\tn + 1, d[n]);\n-\t\t}\n-\t}\n-#endif\n-\n-\tfd \u003d lws_open(\u0022/proc/self/statm\u0022, LWS_O_RDONLY);\n-\tif (fd \u003e\u003d 0) {\n-\t\tchar contents[96], pure[96];\n-\t\tn \u003d (int)read(fd, contents, sizeof(contents) - 1);\n-\t\tif (n \u003e 0) {\n-\t\t\tcontents[n] \u003d '\u005c0';\n-\t\t\tif (contents[n - 1] \u003d\u003d '\u005cn')\n-\t\t\t\tcontents[--n] \u003d '\u005c0';\n-\t\t\tlws_json_purify(pure, contents, sizeof(pure), NULL);\n-\n-\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n-\t\t\t\t\t \u0022\u005c\u0022statm\u005c\u0022: \u005c\u0022%s\u005c\u0022,\u005cn\u0022, pure);\n-\t\t}\n-\t\tclose(fd);\n-\t}\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022\u005c\u0022heap\u005c\u0022:%lld,\u005cn\u005c\u0022contexts\u005c\u0022:[\u005cn\u0022,\n-\t\t\t\t(long long)lws_get_allocated_heap());\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022{ \u0022\n-\t\t\t\t\u0022\u005c\u0022context_uptime\u005c\u0022:\u005c\u0022%llu\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\u0022\u005c\u0022cgi_spawned\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\u0022\u005c\u0022pt_fd_max\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\u0022\u005c\u0022ah_pool_max\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\u0022\u005c\u0022deprecated\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\u0022\u005c\u0022wsi_alive\u005c\u0022:\u005c\u0022\u0022,\n-\t\t\t\t(unsigned long long)(lws_now_usecs() - context-\u003etime_up) /\n-\t\t\t\t\tLWS_US_PER_SEC,\n-\t\t\t\tcontext-\u003ecount_cgi_spawned,\n-\t\t\t\tcontext-\u003efd_limit_per_thread,\n-\t\t\t\tcontext-\u003emax_http_header_pool,\n-\t\t\t\tcontext-\u003edeprecated);\n-\n-\tfor (n \u003d 0; n \u003c LWSLCG_COUNT; n++)\n-\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022%u \u0022,\n-\t\t\t\tcontext-\u003elcg[n].owner.count);\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022\u005c\u0022, \u005c\u0022pt\u005c\u0022:[\u005cn \u0022);\n-\tfor (n \u003d 0; n \u003c context-\u003ecount_threads; n++) {\n-\t\tpt \u003d \u0026context-\u003ept[n];\n-\t\tif (n)\n-\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022,\u0022);\n-\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n-\t\t\t\t\u0022\u005cn {\u005cn\u0022\n-\t\t\t\t\u0022 \u005c\u0022fds_count\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\u0022 \u005c\u0022ah_pool_inuse\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\t\u0022 \u005c\u0022ah_wait_list\u005c\u0022:\u005c\u0022%d\u005c\u0022\u005cn\u0022\n-\t\t\t\t\u0022 }\u0022,\n-\t\t\t\tpt-\u003efds_count,\n-\t\t\t\tpt-\u003ehttp.ah_count_in_use,\n-\t\t\t\tpt-\u003ehttp.ah_wait_list_length);\n-\t}\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022]\u0022);\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022, \u005c\u0022vhosts\u005c\u0022:[\u005cn \u0022);\n-\n-\tfirst \u003d 1;\n-\tvh \u003d context-\u003evhost_list;\n-\tlistening \u003d 0;\n-\tcs \u003d context-\u003econn_stats;\n-\tlws_sum_stats(context, \u0026cs);\n-\twhile (vh) {\n-\n-\t\tif (!hide_vhosts) {\n-\t\t\tif (!first)\n-\t\t\t\tif (buf !\u003d end)\n-\t\t\t\t\t*buf++ \u003d ',';\n-\t\t\tbuf +\u003d lws_json_dump_vhost(vh, buf, lws_ptr_diff(end, buf));\n-\t\t\tfirst \u003d 0;\n-\t\t}\n-\t\tif (vh-\u003elserv_wsi)\n-\t\t\tlistening++;\n-\t\tvh \u003d vh-\u003evhost_next;\n-\t}\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n-\t\t\t\u0022],\u005cn\u005c\u0022listen_wsi\u005c\u0022:\u005c\u0022%d\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022rx\u005c\u0022:\u005c\u0022%llu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022tx\u005c\u0022:\u005c\u0022%llu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h1_conn\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h1_trans\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h2_trans\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022ws_upg\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022rejected\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h2_alpn\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h2_subs\u005c\u0022:\u005c\u0022%lu\u005c\u0022,\u005cn\u0022\n-\t\t\t\u0022 \u005c\u0022h2_upg\u005c\u0022:\u005c\u0022%lu\u005c\u0022\u0022,\n-\t\t\tlistening, cs.rx, cs.tx,\n-\t\t\tcs.h1_conn,\n-\t\t\tcs.h1_trans,\n-\t\t\tcs.h2_trans,\n-\t\t\tcs.ws_upg,\n-\t\t\tcs.rejected,\n-\t\t\tcs.h2_alpn,\n-\t\t\tcs.h2_subs,\n-\t\t\tcs.h2_upg);\n-\n-#ifdef LWS_WITH_CGI\n-\tfor (n \u003d 0; n \u003c context-\u003ecount_threads; n++) {\n-\t\tpt \u003d \u0026context-\u003ept[n];\n-\t\tpcgi \u003d \u0026pt-\u003ehttp.cgi_list;\n-\n-\t\twhile (*pcgi) {\n-\t\t\tpcgi \u003d \u0026(*pcgi)-\u003ecgi_list;\n-\n-\t\t\tcgi_count++;\n-\t\t}\n-\t}\n-#endif\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022,\u005cn \u005c\u0022cgi_alive\u005c\u0022:\u005c\u0022%d\u005c\u0022\u005cn \u0022,\n-\t\t\tcgi_count);\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022}\u0022);\n-\n-\n-\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022]}\u005cn \u0022);\n-\n-\treturn lws_ptr_diff(buf, orig);\n-}\n-\n-#endif\ndiff --git a/lib/core-net/service.c b/lib/core-net/service.c\nindex 62a73a4..5e03705 100644\n--- a/lib/core-net/service.c\n+++ b/lib/core-net/service.c\n@@ -27,31 +27,8 @@\n int\n lws_callback_as_writeable(struct lws *wsi)\n {\n-\tstruct lws_context_per_thread *pt \u003d \u0026wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi];\n \tint n, m;\n \n-\tlws_stats_bump(pt, LWSSTATS_C_WRITEABLE_CB, 1);\n-#if defined(LWS_WITH_STATS)\n-\tif (wsi-\u003eactive_writable_req_us) {\n-\t\tuint64_t ul \u003d lws_now_usecs() -\n-\t\t\t wsi-\u003eactive_writable_req_us;\n-\n-\t\tlws_stats_bump(pt, LWSSTATS_US_WRITABLE_DELAY_AVG, ul);\n-\t\tlws_stats_max(pt, LWSSTATS_US_WORST_WRITABLE_DELAY, ul);\n-\t\twsi-\u003eactive_writable_req_us \u003d 0;\n-\t}\n-#endif\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tif (wsi-\u003ea.context-\u003edetailed_latency_cb \u0026\u0026 lwsi_state_est(wsi)) {\n-\t\tlws_usec_t us \u003d lws_now_usecs();\n-\n-\t\twsi-\u003edetlat.earliest_write_req_pre_write \u003d\n-\t\t\t\t\twsi-\u003edetlat.earliest_write_req;\n-\t\twsi-\u003edetlat.earliest_write_req \u003d 0;\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] \u003d\n-\t\t (uint32_t)(us - wsi-\u003edetlat.earliest_write_req_pre_write);\n-\t}\n-#endif\n \tn \u003d wsi-\u003erole_ops-\u003ewriteable_cb[lwsi_role_server(wsi)];\n \tm \u003d user_callback_handle_rxflow(wsi-\u003ea.protocol-\u003ecallback,\n \t\t\t\t\twsi, (enum lws_callback_reasons) n,\n@@ -827,7 +804,8 @@ lws_service(struct lws_context *context, int timeout_ms)\n \t}\n \tn \u003d lws_plat_service(context, timeout_ms);\n \n-\tpt-\u003einside_service \u003d 0;\n+\tif (n !\u003d -1)\n+\t\tpt-\u003einside_service \u003d 0;\n \n \treturn n;\n }\ndiff --git a/lib/core-net/sorted-usec-list.c b/lib/core-net/sorted-usec-list.c\nindex 4b31221..16ff617 100644\n--- a/lib/core-net/sorted-usec-list.c\n+++ b/lib/core-net/sorted-usec-list.c\n@@ -147,6 +147,8 @@ __lws_sul_service_ripe(lws_dll2_owner_t *own, int own_len, lws_usec_t usnow)\n \t\tlws_dll2_remove(\u0026hit-\u003elist);\n \t\thit-\u003eus \u003d 0;\n \n+\t\t// lwsl_notice(\u0022%s: sul: %p\u005cn\u0022, __func__, hit-\u003ecb);\n+\n \t\tpt-\u003einside_lws_service \u003d 1;\n \t\thit-\u003ecb(hit);\n \t\tpt-\u003einside_lws_service \u003d 0;\ndiff --git a/lib/core-net/stats.c b/lib/core-net/stats.c\ndeleted file mode 100644\nindex 0a7e7b6..0000000\n--- a/lib/core-net/stats.c\n+++ /dev/null\n@@ -1,273 +0,0 @@\n-/*\n- * libwebsockets - small server side websockets and web server implementation\n- *\n- * Copyright (C) 2010 - 2019 Andy Green \u003candy@warmcat.com\u003e\n- *\n- * Permission is hereby granted, free of charge, to any person obtaining a copy\n- * of this software and associated documentation files (the \u0022Software\u0022), to\n- * deal in the Software without restriction, including without limitation the\n- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n- * sell copies of the Software, and to permit persons to whom the Software is\n- * furnished to do so, subject to the following conditions:\n- *\n- * The above copyright notice and this permission notice shall be included in\n- * all copies or substantial portions of the Software.\n- *\n- * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n- * IN THE SOFTWARE.\n- */\n-\n-#include \u0022private-lib-core.h\u0022\n-\n-#if defined(LWS_WITH_STATS)\n-\n-uint64_t\n-lws_stats_get(struct lws_context *context, int index)\n-{\n-\tstruct lws_context_per_thread *pt \u003d \u0026context-\u003ept[0];\n-\n-\tif (index \u003e\u003d LWSSTATS_SIZE)\n-\t\treturn 0;\n-\n-\treturn pt-\u003elws_stats[index];\n-}\n-\n-static const char * stat_names[] \u003d {\n-\t\u0022C_CONNECTIONS\u0022,\n-\t\u0022C_API_CLOSE\u0022,\n-\t\u0022C_API_READ\u0022,\n-\t\u0022C_API_LWS_WRITE\u0022,\n-\t\u0022C_API_WRITE\u0022,\n-\t\u0022C_WRITE_PARTIALS\u0022,\n-\t\u0022C_WRITEABLE_CB_REQ\u0022,\n-\t\u0022C_WRITEABLE_CB_EFF_REQ\u0022,\n-\t\u0022C_WRITEABLE_CB\u0022,\n-\t\u0022C_SSL_CONNECTIONS_FAILED\u0022,\n-\t\u0022C_SSL_CONNECTIONS_ACCEPTED\u0022,\n-\t\u0022C_SSL_CONNECTIONS_ACCEPT_SPIN\u0022,\n-\t\u0022C_SSL_CONNS_HAD_RX\u0022,\n-\t\u0022C_TIMEOUTS\u0022,\n-\t\u0022C_SERVICE_ENTRY\u0022,\n-\t\u0022B_READ\u0022,\n-\t\u0022B_WRITE\u0022,\n-\t\u0022B_PARTIALS_ACCEPTED_PARTS\u0022,\n-\t\u0022US_SSL_ACCEPT_LATENCY_AVG\u0022,\n-\t\u0022US_WRITABLE_DELAY_AVG\u0022,\n-\t\u0022US_WORST_WRITABLE_DELAY\u0022,\n-\t\u0022US_SSL_RX_DELAY_AVG\u0022,\n-\t\u0022C_PEER_LIMIT_AH_DENIED\u0022,\n-\t\u0022C_PEER_LIMIT_WSI_DENIED\u0022,\n-\t\u0022C_CONNECTIONS_CLIENT\u0022,\n-\t\u0022C_CONNECTIONS_CLIENT_FAILED\u0022,\n-};\n-\n-static int\n-quantify(struct lws_context *context, int tsi, char *p, int len, int idx,\n-\t uint64_t *sum)\n-{\n-\tconst lws_humanize_unit_t *schema \u003d humanize_schema_si;\n-\tstruct lws_context_per_thread *pt \u003d \u0026context-\u003ept[tsi];\n-\tuint64_t u, u1;\n-\n-\tlws_pt_stats_lock(pt);\n-\tu \u003d pt-\u003elws_stats[idx];\n-\n-\t/* it's supposed to be an average? */\n-\n-\tswitch (idx) {\n-\tcase LWSSTATS_US_SSL_ACCEPT_LATENCY_AVG:\n-\t\tu1 \u003d pt-\u003elws_stats[LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED];\n-\t\tif (u1)\n-\t\t\tu \u003d u / u1;\n-\t\tbreak;\n-\tcase LWSSTATS_US_SSL_RX_DELAY_AVG:\n-\t\tu1 \u003d pt-\u003elws_stats[LWSSTATS_C_SSL_CONNS_HAD_RX];\n-\t\tif (u1)\n-\t\t\tu \u003d u / u1;\n-\t\tbreak;\n-\tcase LWSSTATS_US_WRITABLE_DELAY_AVG:\n-\t\tu1 \u003d pt-\u003elws_stats[LWSSTATS_C_WRITEABLE_CB];\n-\t\tif (u1)\n-\t\t\tu \u003d u / u1;\n-\t\tbreak;\n-\t}\n-\tlws_pt_stats_unlock(pt);\n-\n-\t*sum +\u003d u;\n-\n-\tswitch (stat_names[idx][0]) {\n-\tcase 'U':\n-\t\tschema \u003d humanize_schema_us;\n-\t\tbreak;\n-\tcase 'B':\n-\t\tschema \u003d humanize_schema_si_bytes;\n-\t\tbreak;\n-\t}\n-\n-\treturn lws_humanize(p, len, u, schema);\n-}\n-\n-\n-void\n-lws_stats_log_dump(struct lws_context *context)\n-{\n-\tstruct lws_vhost *v \u003d context-\u003evhost_list;\n-\tuint64_t summary[LWSSTATS_SIZE];\n-\tchar bufline[128], *p, *end \u003d bufline + sizeof(bufline) - 1;\n-\tint n, m;\n-\n-\tif (!context-\u003eupdated)\n-\t\treturn;\n-\n-\tcontext-\u003eupdated \u003d 0;\n-\tmemset(summary, 0, sizeof(summary));\n-\n-\tlwsl_notice(\u0022\u005cn\u0022);\n-\tlwsl_notice(\u0022LWS internal statistics dump -----\u003e\u005cn\u0022);\n-\tfor (n \u003d 0; n \u003c (int)LWS_ARRAY_SIZE(stat_names); n++) {\n-\t\tuint64_t u \u003d 0;\n-\n-\t\t/* if it's all zeroes, don't report it */\n-\n-\t\tfor (m \u003d 0; m \u003c context-\u003ecount_threads; m++) {\n-\t\t\tstruct lws_context_per_thread *pt \u003d \u0026context-\u003ept[m];\n-\n-\t\t\tu |\u003d pt-\u003elws_stats[n];\n-\t\t}\n-\t\tif (!u)\n-\t\t\tcontinue;\n-\n-\t\tp \u003d bufline;\n-\t\tp +\u003d lws_snprintf(p, lws_ptr_diff(end, p), \u0022%28s: \u0022,\n-\t\t\t\t stat_names[n]);\n-\n-\t\tfor (m \u003d 0; m \u003c context-\u003ecount_threads; m++)\n-\t\t\tquantify(context, m, p, lws_ptr_diff(end, p), n, \u0026summary[n]);\n-\n-\t\tlwsl_notice(\u0022%s\u005cn\u0022, bufline);\n-\t}\n-\n-\tlwsl_notice(\u0022Simultaneous SSL restriction: %8d/%d\u005cn\u0022,\n-\t\t\tcontext-\u003esimultaneous_ssl,\n-\t\t\tcontext-\u003esimultaneous_ssl_restriction);\n-\n-\twhile (v) {\n-\t\tif (v-\u003elserv_wsi \u0026\u0026\n-\t\t v-\u003elserv_wsi-\u003eposition_in_fds_table !\u003d LWS_NO_FDS_POS) {\n-\n-\t\t\tstruct lws_context_per_thread *pt \u003d\n-\t\t\t\t\t\u0026context-\u003ept[(int)v-\u003elserv_wsi-\u003etsi];\n-\t\t\tstruct lws_pollfd *pfd;\n-\n-\t\t\tpfd \u003d \u0026pt-\u003efds[v-\u003elserv_wsi-\u003eposition_in_fds_table];\n-\n-\t\t\tlwsl_notice(\u0022 Listen port %d actual POLLIN: %d\u005cn\u0022,\n-\t\t\t\t v-\u003elisten_port,\n-\t\t\t\t (int)pfd-\u003eevents \u0026 LWS_POLLIN);\n-\t\t}\n-\n-\t\tv \u003d v-\u003evhost_next;\n-\t}\n-\n-\tfor (n \u003d 0; n \u003c context-\u003ecount_threads; n++) {\n-\t\tstruct lws_context_per_thread *pt \u003d \u0026context-\u003ept[n];\n-\t\tstruct lws *wl;\n-\t\tint m \u003d 0;\n-\n-\t\tlwsl_notice(\u0022PT %d\u005cn\u0022, n + 1);\n-\n-\t\tlws_pt_lock(pt, __func__);\n-\n-\t\tlwsl_notice(\u0022 AH in use / max: %d / %d\u005cn\u0022,\n-\t\t\t\tpt-\u003ehttp.ah_count_in_use,\n-\t\t\t\tcontext-\u003emax_http_header_pool);\n-\n-\t\twl \u003d pt-\u003ehttp.ah_wait_list;\n-\t\twhile (wl) {\n-\t\t\tm++;\n-\t\t\twl \u003d wl-\u003ehttp.ah_wait_list;\n-\t\t}\n-\n-\t\tlwsl_notice(\u0022 AH wait list count / actual: %d / %d\u005cn\u0022,\n-\t\t\t\tpt-\u003ehttp.ah_wait_list_length, m);\n-\n-\t\tlws_pt_unlock(pt);\n-\t}\n-\n-#if defined(LWS_WITH_PEER_LIMITS)\n-\tm \u003d 0;\n-\tfor (n \u003d 0; n \u003c (int)context-\u003epl_hash_elements; n++) {\n-\t\tlws_start_foreach_llp(struct lws_peer **, peer,\n-\t\t\t\t context-\u003epl_hash_table[n]) {\n-\t\t\tm++;\n-\t\t} lws_end_foreach_llp(peer, next);\n-\t}\n-\n-\tlwsl_notice(\u0022 Peers: total active %d\u005cn\u0022, m);\n-\tif (m \u003e 10) {\n-\t\tm \u003d 10;\n-\t\tlwsl_notice(\u0022 (showing 10 peers only)\u005cn\u0022);\n-\t}\n-\n-\tif (m) {\n-\t\tfor (n \u003d 0; n \u003c (int)context-\u003epl_hash_elements; n++) {\n-\t\t\tchar buf[72];\n-\n-\t\t\tlws_start_foreach_llp(struct lws_peer **, peer,\n-\t\t\t\t\t context-\u003epl_hash_table[n]) {\n-\t\t\t\tstruct lws_peer *df \u003d *peer;\n-\n-\t\t\t\tif (!lws_plat_inet_ntop(df-\u003eaf, df-\u003eaddr, buf,\n-\t\t\t\t\t\t\tsizeof(buf) - 1))\n-\t\t\t\t\tstrcpy(buf, \u0022unknown\u0022);\n-#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)\n-\t\t\t\tlwsl_notice(\u0022 peer %s: count wsi: %d, count ah: %d\u005cn\u0022,\n-\t\t\t\t\t buf, df-\u003ecount_wsi,\n-\t\t\t\t\t df-\u003ehttp.count_ah);\n-#else\n-\t\t\t\tlwsl_notice(\u0022 peer %s: count wsi: %d\u005cn\u0022,\n-\t\t\t\t\t buf, df-\u003ecount_wsi);\n-#endif\n-\n-\t\t\t\tif (!--m)\n-\t\t\t\t\tbreak;\n-\t\t\t} lws_end_foreach_llp(peer, next);\n-\t\t}\n-\t}\n-#endif\n-\n-\tlwsl_notice(\u0022\u005cn\u0022);\n-}\n-\n-void\n-lws_stats_bump(struct lws_context_per_thread *pt, int i, uint64_t bump)\n-{\n-\tlws_pt_stats_lock(pt);\n-\tpt-\u003elws_stats[i] +\u003d bump;\n-\tif (i !\u003d LWSSTATS_C_SERVICE_ENTRY) {\n-\t\tpt-\u003eupdated \u003d 1;\n-\t\tpt-\u003econtext-\u003eupdated \u003d 1;\n-\t}\n-\tlws_pt_stats_unlock(pt);\n-}\n-\n-void\n-lws_stats_max(struct lws_context_per_thread *pt, int index, uint64_t val)\n-{\n-\tlws_pt_stats_lock(pt);\n-\tif (val \u003e pt-\u003elws_stats[index]) {\n-\t\tpt-\u003elws_stats[index] \u003d val;\n-\t\tpt-\u003eupdated \u003d 1;\n-\t\tpt-\u003econtext-\u003eupdated \u003d 1;\n-\t}\n-\tlws_pt_stats_unlock(pt);\n-}\n-\n-#endif\n-\n-\ndiff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c\nindex a2ce30b..144b6ea 100644\n--- a/lib/core-net/vhost.c\n+++ b/lib/core-net/vhost.c\n@@ -1,7 +1,7 @@\n /*\n * libwebsockets - small server side websockets and web server implementation\n *\n- * Copyright (C) 2010 - 2019 Andy Green \u003candy@warmcat.com\u003e\n+ * Copyright (C) 2010 - 2021 Andy Green \u003candy@warmcat.com\u003e\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \u0022Software\u0022), to\n@@ -119,9 +119,13 @@ lws_role_call_alpn_negotiated(struct lws *wsi, const char *alpn)\n \n \tLWS_FOR_EVERY_AVAILABLE_ROLE_START(ar)\n \t\tif (ar-\u003ealpn \u0026\u0026 !strcmp(ar-\u003ealpn, alpn) \u0026\u0026\n-\t\t lws_rops_fidx(ar, LWS_ROPS_alpn_negotiated))\n+\t\t lws_rops_fidx(ar, LWS_ROPS_alpn_negotiated)) {\n+#if defined(LWS_WITH_SERVER)\n+\t\t\tlws_metrics_tag_wsi_add(wsi, \u0022upg\u0022, ar-\u003ename);\n+#endif\n \t\t\treturn (lws_rops_func_fidx(ar, LWS_ROPS_alpn_negotiated)).\n \t\t\t\t\t\t alpn_negotiated(wsi, alpn);\n+\t\t}\n \tLWS_FOR_EVERY_AVAILABLE_ROLE_END;\n #endif\n \treturn 0;\n@@ -305,6 +309,8 @@ lws_vhd_find_by_pvo(struct lws_context *cx, const char *protname,\n \tvh \u003d cx-\u003evhost_list;\n \twhile (vh) {\n \n+\t\tif (vh-\u003eprotocol_vh_privs) {\n+\n \t\tfor (n \u003d 0; n \u003c vh-\u003ecount_protocols; n++) {\n \t\t\tconst struct lws_protocol_vhost_options *pv;\n \n@@ -313,7 +319,11 @@ lws_vhd_find_by_pvo(struct lws_context *cx, const char *protname,\n \n \t\t\t/* this vh has an instance of the required protocol */\n \n-\t\t\tpv \u003d lws_pvo_search(vh-\u003epvo, pvo_name);\n+\t\t\tpv \u003d lws_pvo_search(vh-\u003epvo, protname);\n+\t\t\tif (!pv)\n+\t\t\t\tcontinue;\n+\n+\t\t\tpv \u003d lws_pvo_search(pv-\u003eoptions, pvo_name);\n \t\t\tif (!pv)\n \t\t\t\tcontinue;\n \n@@ -326,6 +336,8 @@ lws_vhd_find_by_pvo(struct lws_context *cx, const char *protname,\n \t\t\t\t */\n \t\t\t\treturn vh-\u003eprotocol_vh_privs[n];\n \t\t}\n+\t\t} else\n+\t\t\tlwsl_notice(\u0022%s: no privs yet on %s\u005cn\u0022, __func__, lws_vh_tag(vh));\n \t\tvh \u003d vh-\u003evhost_next;\n \t}\n \n@@ -460,7 +472,7 @@ lws_protocol_init(struct lws_context *context)\n \n \tcontext-\u003edoing_protocol_init \u003d 1;\n \n-\tlwsl_notice(\u0022%s\u005cn\u0022, __func__);\n+\tlwsl_info(\u0022%s\u005cn\u0022, __func__);\n \n \twhile (vh) {\n \n@@ -538,10 +550,7 @@ lws_create_vhost(struct lws_context *context,\n \tstruct lws_protocols *lwsp;\n \tint m, f \u003d !info-\u003epvo, fx \u003d 0, abs_pcol_count \u003d 0, sec_pcol_count \u003d 0;\n \tchar buf[96];\n-#if defined(LWS_CLIENT_HTTP_PROXYING) \u0026\u0026 defined(LWS_WITH_CLIENT) \u005c\n-\t\u0026\u0026 defined(LWS_HAVE_GETENV)\n \tchar *p;\n-#endif\n #if defined(LWS_WITH_SYS_ASYNC_DNS)\n \textern struct lws_protocols lws_async_dns_protocol;\n #endif\n@@ -572,6 +581,19 @@ lws_create_vhost(struct lws_context *context,\n \t\tvh-\u003ename \u003d \u0022default\u0022;\n \telse\n \t\tvh-\u003ename \u003d info-\u003evhost_name;\n+\t{\n+\t\tchar *end \u003d buf + sizeof(buf) - 1;\n+\t\tp \u003d buf;\n+\n+\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022%s\u0022, vh-\u003ename);\n+\t\tif (info-\u003eiface)\n+\t\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022|%s\u0022, info-\u003eiface);\n+\t\tif (info-\u003eport \u0026\u0026 !(info-\u003eport \u0026 0xffff))\n+\t\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022|%u\u0022, info-\u003eport);\n+\t}\n+\n+\t__lws_lc_tag(\u0026context-\u003elcg[LWSLCG_VHOST], \u0026vh-\u003elc, \u0022%s|%s|%d\u0022, buf,\n+\t\t\tinfo-\u003eiface ? info-\u003eiface : \u0022\u0022, info-\u003eport);\n \n #if defined(LWS_WITH_SYS_FAULT_INJECTION)\n \tvh-\u003efi.name \u003d \u0022vh\u0022;\n@@ -584,9 +606,6 @@ lws_create_vhost(struct lws_context *context,\n \t\tlws_fi_import(\u0026vh-\u003efi, info-\u003efi);\n #endif\n \n-\t__lws_lc_tag(\u0026context-\u003elcg[LWSLCG_VHOST], \u0026vh-\u003elc, \u0022%s|%s|%d\u0022, vh-\u003ename,\n-\t\t\tinfo-\u003eiface ? info-\u003eiface : \u0022\u0022, info-\u003eport);\n-\n #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)\n \tvh-\u003ehttp.error_document_404 \u003d info-\u003eerror_document_404;\n #endif\n@@ -804,6 +823,23 @@ lws_create_vhost(struct lws_context *context,\n \tvh-\u003ehttp.mount_list \u003d info-\u003emounts;\n #endif\n \n+#if defined(LWS_WITH_SYS_METRICS) \u0026\u0026 defined(LWS_WITH_SERVER)\n+\t{\n+\t\tchar *end \u003d buf + sizeof(buf) - 1;\n+\t\tp \u003d buf;\n+\n+\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022vh.%s\u0022, vh-\u003ename);\n+\t\tif (info-\u003eiface)\n+\t\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022.%s\u0022, info-\u003eiface);\n+\t\tif (info-\u003eport \u0026\u0026 !(info-\u003eport \u0026 0xffff))\n+\t\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022.%u\u0022, info-\u003eport);\n+\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022.rx\u0022);\n+\t\tvh-\u003emt_traffic_rx \u003d lws_metric_create(context, 0, buf);\n+\t\tp[-2] \u003d 't';\n+\t\tvh-\u003emt_traffic_tx \u003d lws_metric_create(context, 0, buf);\n+\t}\n+#endif\n+\n #ifdef LWS_WITH_UNIX_SOCK\n \tif (LWS_UNIX_SOCK_ENABLED(vh)) {\n \t\tlwsl_info(\u0022Creating Vhost '%s' path \u005c\u0022%s\u005c\u0022, %d protocols\u005cn\u0022,\n@@ -909,7 +945,7 @@ lws_create_vhost(struct lws_context *context,\n \t\tgoto bail1;\n \t}\n #if defined(LWS_WITH_SERVER)\n-\tlws_context_lock(context, \u0022create_vhost\u0022);\n+\tlws_context_lock(context, __func__);\n \tn \u003d _lws_vhost_init_server(info, vh);\n \tlws_context_unlock(context);\n \tif (n \u003c 0) {\n@@ -1397,6 +1433,11 @@ __lws_vhost_destroy2(struct lws_vhost *vh)\n \tlws_dll2_foreach_safe(\u0026vh-\u003eabstract_instances_owner, NULL, destroy_ais);\n #endif\n \n+#if defined(LWS_WITH_SERVER) \u0026\u0026 defined(LWS_WITH_SYS_METRICS)\n+\tlws_metric_destroy(\u0026vh-\u003emt_traffic_rx, 0);\n+\tlws_metric_destroy(\u0026vh-\u003emt_traffic_tx, 0);\n+#endif\n+\n \tlws_dll2_remove(\u0026vh-\u003evh_being_destroyed_list);\n \n #if defined(LWS_WITH_SYS_FAULT_INJECTION)\n@@ -1404,6 +1445,7 @@ __lws_vhost_destroy2(struct lws_vhost *vh)\n #endif\n \n \t__lws_lc_untag(\u0026vh-\u003elc);\n+\n \tmemset(vh, 0, sizeof(*vh));\n \tlws_free(vh);\n }\ndiff --git a/lib/core-net/wsi-timeout.c b/lib/core-net/wsi-timeout.c\nindex a51a34e..70ef6d8 100644\n--- a/lib/core-net/wsi-timeout.c\n+++ b/lib/core-net/wsi-timeout.c\n@@ -81,9 +81,6 @@ lws_sul_wsitimeout_cb(lws_sorted_usec_list_t *sul)\n \tstruct lws *wsi \u003d lws_container_of(sul, struct lws, sul_timeout);\n \tstruct lws_context_per_thread *pt \u003d \u0026wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi];\n \n-\tif (wsi-\u003epending_timeout !\u003d PENDING_TIMEOUT_USER_OK)\n-\t\tlws_stats_bump(pt, LWSSTATS_C_TIMEOUTS, 1);\n-\n \t/* no need to log normal idle keepalive timeout */\n //\t\tif (wsi-\u003epending_timeout !\u003d PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE)\n #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)\ndiff --git a/lib/core-net/wsi.c b/lib/core-net/wsi.c\nindex 4bad83d..5392ef7 100644\n--- a/lib/core-net/wsi.c\n+++ b/lib/core-net/wsi.c\n@@ -342,9 +342,9 @@ lws_rx_flow_control(struct lws *wsi, int _enable)\n \n \t/* any bit set in rxflow_bitmap DISABLEs rxflow control */\n \tif (en \u0026 LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT)\n-\t\twsi-\u003erxflow_bitmap \u0026\u003d (uint8_t)~(en \u0026 0xff);\n+\t\twsi-\u003erxflow_bitmap \u003d (uint8_t)(wsi-\u003erxflow_bitmap \u0026 ~(en \u0026 0xff));\n \telse\n-\t\twsi-\u003erxflow_bitmap |\u003d (uint8_t)(en \u0026 0xff);\n+\t\twsi-\u003erxflow_bitmap \u003d (uint8_t)(wsi-\u003erxflow_bitmap | (en \u0026 0xff));\n \n \tif ((LWS_RXFLOW_PENDING_CHANGE | (!wsi-\u003erxflow_bitmap)) \u003d\u003d\n \t wsi-\u003erxflow_change_to)\ndiff --git a/lib/core/context.c b/lib/core/context.c\nindex e556953..1fddce6 100644\n--- a/lib/core/context.c\n+++ b/lib/core/context.c\n@@ -53,20 +53,6 @@ lws_get_library_version(void)\n \treturn library_version;\n }\n \n-#if defined(LWS_WITH_STATS)\n-static void\n-lws_sul_stats_cb(lws_sorted_usec_list_t *sul)\n-{\n-\tstruct lws_context_per_thread *pt \u003d lws_container_of(sul,\n-\t\t\tstruct lws_context_per_thread, sul_stats);\n-\n-\tlws_stats_log_dump(pt-\u003econtext);\n-\n-\t__lws_sul_insert_us(\u0026pt-\u003ept_sul_owner[LWSSULLI_MISS_IF_SUSPENDED],\n-\t\t\t \u0026pt-\u003esul_stats, 10 * LWS_US_PER_SEC);\n-}\n-#endif\n-\n #if defined(LWS_WITH_NETWORK)\n \n #if defined(LWS_WITH_SYS_STATE)\n@@ -379,10 +365,11 @@ lws_create_context(const struct lws_context_creation_info *info)\n \tpid_t pid_daemon \u003d get_daemonize_pid();\n #endif\n #if defined(LWS_WITH_NETWORK)\n+\tconst lws_plugin_evlib_t *plev \u003d NULL;\n \tunsigned short count_threads \u003d 1;\n \tuint8_t *u;\n \tuint16_t us_wait_resolution \u003d 0;\n-#endif\n+\n #if defined(__ANDROID__)\n \tstruct rlimit rt;\n #endif\n@@ -394,9 +381,10 @@ lws_create_context(const struct lws_context_creation_info *info)\n \t\ts1 \u003d 4096,\n #endif\n \t\tsize \u003d sizeof(struct lws_context);\n+#endif\n+\n \tint n;\n \tunsigned int lpf \u003d info-\u003efd_limit_per_thread;\n-\tconst lws_plugin_evlib_t *plev \u003d NULL;\n #if defined(LWS_WITH_EVLIB_PLUGINS) \u0026\u0026 defined(LWS_WITH_EVENT_LIBS)\n \tstruct lws_plugin\t\t*evlib_plugin_list \u003d NULL;\n #if defined(_DEBUG)\n@@ -429,10 +417,6 @@ lws_create_context(const struct lws_context_creation_info *info)\n \t\ts \u003d \u0022IPV6-off\u0022;\n #endif\n \n-#if defined(LWS_WITH_STATS)\n-\tlwsl_info(\u0022 LWS_WITH_STATS : on\u005cn\u0022);\n-#endif\n-\n \tlwsl_notice(\u0022%s%s\u005cn\u0022, opts_str, s);\n \n \tif (lws_plat_context_early_init())\n@@ -453,7 +437,6 @@ lws_create_context(const struct lws_context_creation_info *info)\n #if !defined(LWS_PLAT_FREERTOS)\n \tsize +\u003d (count_threads * sizeof(struct lws));\n #endif\n-#endif /* network */\n \n #if defined(LWS_WITH_POLL)\n \t{\n@@ -655,6 +638,10 @@ lws_create_context(const struct lws_context_creation_info *info)\n \tcontext-\u003elcg[LWSLCG_VHOST].tag_prefix \u003d \u0022vh\u0022;\n \tcontext-\u003elcg[LWSLCG_WSI_SERVER].tag_prefix \u003d \u0022wsisrv\u0022; /* adopted */\n \n+#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT)\n+\tcontext-\u003elcg[LWSLCG_WSI_MUX].tag_prefix \u003d \u0022mux\u0022, /* a mux child wsi */\n+#endif\n+\n #if defined(LWS_WITH_CLIENT)\n \tcontext-\u003elcg[LWSLCG_WSI_CLIENT].tag_prefix \u003d \u0022wsicli\u0022;\n #endif\n@@ -675,6 +662,77 @@ lws_create_context(const struct lws_context_creation_info *info)\n #endif\n #endif\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t/*\n+\t * If we're not using secure streams, we can still pass in a linked-\n+\t * list of metrics policies\n+\t */\n+\tcontext-\u003emetrics_policies \u003d info-\u003emetrics_policies;\n+\tcontext-\u003emetrics_prefix \u003d info-\u003emetrics_prefix;\n+\n+\tcontext-\u003emt_service \u003d lws_metric_create(context,\n+\t\t\t\t\tLWSMTFL_REPORT_DUTY_WALLCLOCK_US |\n+\t\t\t\t\tLWSMTFL_REPORT_ONLY_GO, \u0022cpu.svc\u0022);\n+\n+#if defined(LWS_WITH_CLIENT)\n+\n+\tcontext-\u003emt_conn_dns \u003d lws_metric_create(context,\n+\t\t\t\t\t\t LWSMTFL_REPORT_MEAN |\n+\t\t\t\t\t\t LWSMTFL_REPORT_DUTY_WALLCLOCK_US,\n+\t\t\t\t\t\t \u0022n.cn.dns\u0022);\n+\tcontext-\u003emt_conn_tcp \u003d lws_metric_create(context,\n+\t\t\t\t\t\t LWSMTFL_REPORT_MEAN |\n+\t\t\t\t\t\t LWSMTFL_REPORT_DUTY_WALLCLOCK_US,\n+\t\t\t\t\t\t \u0022n.cn.tcp\u0022);\n+\tcontext-\u003emt_conn_tls \u003d lws_metric_create(context,\n+\t\t\t\t\t\t LWSMTFL_REPORT_MEAN |\n+\t\t\t\t\t\t LWSMTFL_REPORT_DUTY_WALLCLOCK_US,\n+\t\t\t\t\t\t \u0022n.cn.tls\u0022);\n+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)\n+\tcontext-\u003emt_http_txn \u003d lws_metric_create(context,\n+\t\t\t\t\t\t LWSMTFL_REPORT_MEAN |\n+\t\t\t\t\t\t LWSMTFL_REPORT_DUTY_WALLCLOCK_US,\n+\t\t\t\t\t\t \u0022n.http.txn\u0022);\n+#endif\n+\n+\tcontext-\u003emth_conn_failures \u003d lws_metric_create(context,\n+\t\t\t\t\tLWSMTFL_REPORT_HIST, \u0022n.cn.failures\u0022);\n+\n+#if defined(LWS_WITH_SYS_ASYNC_DNS)\n+\tcontext-\u003emt_adns_cache \u003d lws_metric_create(context,\n+\t\t\t\t\t\t LWSMTFL_REPORT_MEAN |\n+\t\t\t\t\t\t LWSMTFL_REPORT_DUTY_WALLCLOCK_US,\n+\t\t\t\t\t\t \u0022n.cn.adns\u0022);\n+#endif\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\tcontext-\u003emth_ss_conn \u003d lws_metric_create(context, LWSMTFL_REPORT_HIST,\n+\t\t\t\t\t\t \u0022n.ss.conn\u0022);\n+#endif\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\tcontext-\u003emt_ss_cliprox_conn \u003d lws_metric_create(context,\n+\t\t\tLWSMTFL_REPORT_HIST,\n+\t\t\t\t\t\t\t\u0022n.ss.cliprox.conn\u0022);\n+\tcontext-\u003emt_ss_cliprox_paylat \u003d lws_metric_create(context,\n+\t\t\t\t\t\t\t LWSMTFL_REPORT_MEAN |\n+\t\t\t\t\t\t\t LWSMTFL_REPORT_DUTY_WALLCLOCK_US,\n+\t\t\t\t\t\t\t \u0022n.ss.cliprox.paylat\u0022);\n+\tcontext-\u003emt_ss_proxcli_paylat \u003d lws_metric_create(context,\n+\t\t\t\t\t\t\t LWSMTFL_REPORT_MEAN |\n+\t\t\t\t\t\t\t LWSMTFL_REPORT_DUTY_WALLCLOCK_US,\n+\t\t\t\t\t\t\t \u0022n.ss.proxcli.paylat\u0022);\n+#endif\n+\n+#endif /* network + metrics + client */\n+\n+#if defined(LWS_WITH_SERVER)\n+\tcontext-\u003emth_srv \u003d lws_metric_create(context,\n+\t\t\t\t\t LWSMTFL_REPORT_HIST, \u0022n.srv\u0022);\n+#endif /* network + metrics + server */\n+\n+#endif /* network + metrics */\n+\n+#endif /* network */\n+\n \t/*\n \t * Proxy group\n \t */\n@@ -713,11 +771,7 @@ lws_create_context(const struct lws_context_creation_info *info)\n #if defined(LWS_WITH_NETWORK)\n \tcontext-\u003eundestroyed_threads \u003d count_threads;\n \tcontext-\u003ecount_threads \u003d count_threads;\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tcontext-\u003edetailed_latency_cb \u003d info-\u003edetailed_latency_cb;\n-\tcontext-\u003edetailed_latency_filepath \u003d info-\u003edetailed_latency_filepath;\n-\tcontext-\u003elatencies_fd \u003d -1;\n-#endif\n+\n #if defined(LWS_ROLE_WS) \u0026\u0026 defined(LWS_WITHOUT_EXTENSIONS)\n if (info-\u003eextensions)\n lwsl_warn(\u0022%s: LWS_WITHOUT_EXTENSIONS but extensions ptr set\u005cn\u0022, __func__);\n@@ -1144,12 +1198,6 @@ lws_create_context(const struct lws_context_creation_info *info)\n #endif\n #endif\n \n-#if defined(LWS_WITH_STATS)\n-\tcontext-\u003ept[0].sul_stats.cb \u003d lws_sul_stats_cb;\n-\t__lws_sul_insert_us(\u0026context-\u003ept[0].pt_sul_owner[LWSSULLI_MISS_IF_SUSPENDED],\n-\t\t\t \u0026context-\u003ept[0].sul_stats, 10 * LWS_US_PER_SEC);\n-#endif\n-\n #if defined(LWS_HAVE_SYS_CAPABILITY_H) \u0026\u0026 defined(LWS_HAVE_LIBCAP)\n \tmemcpy(context-\u003ecaps, info-\u003ecaps, sizeof(context-\u003ecaps));\n \tcontext-\u003ecount_caps \u003d info-\u003ecount_caps;\n@@ -1364,8 +1412,10 @@ bail_libuv_aware:\n \treturn NULL;\n #endif\n \n+#if defined(LWS_WITH_NETWORK)\n fail_event_libs:\n \tlwsl_err(\u0022Requested event library support not configured\u005cn\u0022);\n+#endif\n \n free_context_fail:\n \tlws_free(context);\n@@ -1598,6 +1648,11 @@ lws_context_destroy(struct lws_context *context)\n \t\tcontext-\u003ebeing_destroyed \u003d 1;\n \n #if defined(LWS_WITH_NETWORK)\n+\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tlws_metrics_dump(context);\n+#endif\n+\n \t\t/*\n \t\t * Close any vhost listen wsi\n \t\t *\n@@ -1819,7 +1874,6 @@ next:\n \t\tlwsl_debug(\u0022%p: post pdl\u005cn\u0022, __func__);\n #endif\n \n-\t\tlws_stats_log_dump(context);\n #if defined(LWS_WITH_NETWORK)\n \t\tlws_ssl_context_destroy(context);\n #endif\n@@ -1982,6 +2036,10 @@ next:\n \t\tlws_mutex_refcount_destroy(\u0026context-\u003emr);\n #endif\n \n+#if defined(LWS_WITH_SYS_METRICS) \u0026\u0026 defined(LWS_WITH_NETWORK)\n+\t\tlws_metrics_destroy(context);\n+#endif\n+\n \t\tif (context-\u003eexternal_baggage_free_on_destroy)\n \t\t\tfree(context-\u003eexternal_baggage_free_on_destroy);\n \ndiff --git a/lib/core/logs.c b/lib/core/logs.c\nindex 019858d..e6b6025 100644\n--- a/lib/core/logs.c\n+++ b/lib/core/logs.c\n@@ -55,6 +55,42 @@ __lws_lc_tag(lws_lifecycle_group_t *grp, lws_lifecycle_t *lc,\n \tva_list ap;\n \tint n \u003d 1;\n \n+\tif (*lc-\u003egutag \u003d\u003d '[') {\n+\t\t/* appending inside [] */\n+\n+\t\tchar *cp \u003d strchr(lc-\u003egutag, ']');\n+\t\tchar rend[96];\n+\t\tsize_t ll, k;\n+\t\tint n;\n+\n+\t\tif (!cp)\n+\t\t\treturn;\n+\n+\t\t/* length of closing brace and anything else after it */\n+\t\tk \u003d strlen(cp);\n+\n+\t\t/* compute the remaining gutag unused */\n+\t\tll \u003d sizeof(lc-\u003egutag) - lws_ptr_diff_size_t(cp, lc-\u003egutag) - k - 1;\n+\t\tif (ll \u003e sizeof(rend) - 1)\n+\t\t\tll \u003d sizeof(rend) - 1;\n+\t\tva_start(ap, format);\n+\t\tn \u003d vsnprintf(rend, ll, format, ap);\n+\t\tva_end(ap);\n+\n+\t\tif ((unsigned int)n \u003e ll)\n+\t\t\tn \u003d (int)ll;\n+\n+\t\t/* shove the trailer up by what we added */\n+\t\tmemmove(cp + n, cp, k);\n+\t\tassert(k + (unsigned int)n \u003c sizeof(lc-\u003egutag));\n+\t\tcp[k + (unsigned int)n] \u003d '\u005c0';\n+\t\t/* copy what we added into place */\n+\t\tmemcpy(cp, rend, (unsigned int)n);\n+\n+\t\treturn;\n+\t}\n+\n+\tassert(grp);\n \tassert(grp-\u003etag_prefix); /* lc group must have a tag prefix string */\n \n \tlc-\u003egutag[0] \u003d '[';\ndiff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h\nindex 47ad0b5..0ec9089 100644\n--- a/lib/core/private-lib-core.h\n+++ b/lib/core/private-lib-core.h\n@@ -139,6 +139,41 @@\n \n #include \u0022libwebsockets.h\u0022\n \n+/*\n+ * lws_dsh\n+*/\n+\n+typedef struct lws_dsh_obj_head {\n+\tlws_dll2_owner_t\t\towner;\n+\tsize_t\t\t\t\ttotal_size; /* for this kind in dsh */\n+\tint\t\t\t\tkind;\n+} lws_dsh_obj_head_t;\n+\n+typedef struct lws_dsh_obj {\n+\tlws_dll2_t\t\t\tlist;\t/* must be first */\n+\tstruct lws_dsh\t \t\t*dsh;\t/* invalid when on free list */\n+\tsize_t\t\t\t\tsize;\t/* invalid when on free list */\n+\tsize_t\t\t\t\tasize;\n+\tint\t\t\t\tkind; /* so we can account at free */\n+} lws_dsh_obj_t;\n+\n+typedef struct lws_dsh {\n+\tlws_dll2_t\t\t\tlist;\n+\tuint8_t\t\t\t\t*buf;\n+\tlws_dsh_obj_head_t\t\t*oha;\t/* array of object heads/kind */\n+\tsize_t\t\t\t\tbuffer_size;\n+\tsize_t\t\t\t\tlocally_in_use;\n+\tsize_t\t\t\t\tlocally_free;\n+\tint\t\t\t\tcount_kinds;\n+\tuint8_t\t\t\t\tbeing_destroyed;\n+\t/*\n+\t * Overallocations at create:\n+\t *\n+\t * - the buffer itself\n+\t * - the object heads array\n+\t */\n+} lws_dsh_t;\n+\n /*\n *\n * ------ lifecycle defines ------\n@@ -285,6 +320,9 @@ struct lws;\n #include \u0022private-lib-system-fault-injection.h\u0022\n #endif\n \n+#include \u0022private-lib-system-metrics.h\u0022\n+\n+\n struct lws_foreign_thread_pollfd {\n \tstruct lws_foreign_thread_pollfd *next;\n \tint fd_index;\n@@ -328,6 +366,10 @@ enum {\n \n \tLWSLCG_WSI_SERVER,\t\t/* server wsi */\n \n+#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT)\n+\tLWSLCG_WSI_MUX,\t\t\t/* a mux child wsi */\n+#endif\n+\n #if defined(LWS_WITH_CLIENT)\n \tLWSLCG_WSI_CLIENT,\t\t/* client wsi */\n #endif\n@@ -418,21 +460,53 @@ struct lws_context {\n \tstruct http2_settings\t\t\tset;\n #endif\n \n-#if defined(LWS_WITH_SERVER_STATUS)\n-\tstruct lws_conn_stats\t\t\tconn_stats;\n-#endif\n #if LWS_MAX_SMP \u003e 1\n \tstruct lws_mutex_refcount\t\tmr;\n #endif\n \n-#if defined(LWS_WITH_NETWORK)\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_dll2_owner_t\t\t\towner_mtr_dynpol;\n+\t/**\u003c owner for lws_metric_policy_dyn_t (dynamic part of metric pols) */\n+\tlws_dll2_owner_t\t\t\towner_mtr_no_pol;\n+\t/**\u003c owner for lws_metric_pub_t with no policy to bind to */\n+#endif\n \n+#if defined(LWS_WITH_NETWORK)\n /*\n * LWS_WITH_NETWORK \u003d\u003d\u003d\u003d\u003d\u003e\n */\n \n \tlws_dll2_owner_t\t\towner_vh_being_destroyed;\n \n+\tlws_metric_t\t\t\t*mt_service; /* doing service */\n+\tconst lws_metric_policy_t\t*metrics_policies;\n+\tconst char\t\t\t*metrics_prefix;\n+\n+#if defined(LWS_WITH_SYS_METRICS) \u0026\u0026 defined(LWS_WITH_CLIENT)\n+\tlws_metric_t\t\t\t*mt_conn_tcp; /* client tcp conns */\n+\tlws_metric_t\t\t\t*mt_conn_tls; /* client tcp conns */\n+\tlws_metric_t\t\t\t*mt_conn_dns; /* client dns external lookups */\n+\tlws_metric_t\t\t\t*mth_conn_failures; /* histogram of conn failure reasons */\n+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)\n+\tlws_metric_t\t\t\t*mt_http_txn; /* client http transaction */\n+#endif\n+#if defined(LWS_WITH_SYS_ASYNC_DNS)\n+\tlws_metric_t\t\t\t*mt_adns_cache; /* async dns lookup lat */\n+#endif\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\tlws_metric_t\t\t\t*mth_ss_conn; /* SS connection outcomes */\n+#endif\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\tlws_metric_t\t\t\t*mt_ss_cliprox_conn; /* SS cli-\u003eprox conn */\n+\tlws_metric_t\t\t\t*mt_ss_cliprox_paylat; /* cli-\u003eprox payload latency */\n+\tlws_metric_t\t\t\t*mt_ss_proxcli_paylat; /* prox-\u003ecli payload latency */\n+#endif\n+#endif /* client */\n+\n+#if defined(LWS_WITH_SERVER)\n+\tlws_metric_t\t\t\t*mth_srv;\n+#endif\n+\n #if defined(LWS_WITH_EVENT_LIBS)\n \tstruct lws_plugin\t\t*evlib_plugin_list;\n \tvoid\t\t\t\t*evlib_ctx; /* overallocated */\n@@ -498,9 +572,6 @@ struct lws_context {\n \tconst struct lws_tls_ops\t*tls_ops;\n #endif\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tdet_lat_buf_cb_t\t\tdetailed_latency_cb;\n-#endif\n #if defined(LWS_WITH_PLUGINS)\n \tstruct lws_plugin\t\t*plugin_list;\n #endif\n@@ -531,9 +602,6 @@ struct lws_context {\n #if !defined(LWS_PLAT_FREERTOS)\n \tconst char *username, *groupname;\n #endif\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tconst char *detailed_latency_filepath;\n-#endif\n \n #if defined(LWS_AMAZON_RTOS) \u0026\u0026 defined(LWS_WITH_MBEDTLS)\n \tmbedtls_entropy_context mec;\n@@ -589,6 +657,9 @@ struct lws_context {\n \tuint64_t options;\n \n \ttime_t last_ws_ping_pong_check_s;\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\ttime_t\t\t\t\t\tlast_policy;\n+#endif\n \n #if defined(LWS_PLAT_FREERTOS)\n \tunsigned long time_last_state_dump;\n@@ -607,9 +678,6 @@ struct lws_context {\n \tint count_cgi_spawned;\n #endif\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tint latencies_fd;\n-#endif\n \tunsigned int fd_limit_per_thread;\n \tunsigned int timeout_secs;\n \tunsigned int pt_serv_buf_size;\n@@ -667,10 +735,6 @@ struct lws_context {\n \tuint8_t captive_portal_detect_type;\n \n \tuint8_t\t\tdestroy_state; /* enum lws_context_destroy */\n-\n-#if defined(LWS_WITH_STATS)\n-\tuint8_t updated;\n-#endif\n };\n \n #define lws_get_context_protocol(ctx, x) ctx-\u003evhost_list-\u003eprotocols[x]\ndiff --git a/lib/plat/freertos/freertos-service.c b/lib/plat/freertos/freertos-service.c\nindex 1d79301..1bea562 100644\n--- a/lib/plat/freertos/freertos-service.c\n+++ b/lib/plat/freertos/freertos-service.c\n@@ -50,7 +50,6 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)\n \t\treturn 1;\n \n \tpt \u003d \u0026context-\u003ept[tsi];\n-\tlws_stats_bump(pt, LWSSTATS_C_SERVICE_ENTRY, 1);\n \n \t{\n \t\tunsigned long m \u003d lws_now_secs();\n@@ -142,15 +141,6 @@ again:\n \t\t\tn \u003d select(max_fd + 1, \u0026readfds, \u0026writefds, \u0026errfds, ptv);\n \t\t\tn \u003d 0;\n \n-\t#if defined(LWS_WITH_DETAILED_LATENCY)\n-\t\t\t/*\n-\t\t\t * so we can track how long it took before we actually read a POLLIN\n-\t\t\t * that was signalled when we last exited poll()\n-\t\t\t */\n-\t\t\tif (context-\u003edetailed_latency_cb)\n-\t\t\t\tpt-\u003eust_left_poll \u003d lws_now_usecs();\n-\t#endif\n-\n \t\t\tfor (m \u003d 0; m \u003c (int)pt-\u003efds_count; m++) {\n \t\t\t\tc \u003d 0;\n \t\t\t\tif (FD_ISSET(pt-\u003efds[m].fd, \u0026readfds)) {\ndiff --git a/lib/plat/unix/unix-service.c b/lib/plat/unix/unix-service.c\nindex c11bbad..49d0692 100644\n--- a/lib/plat/unix/unix-service.c\n+++ b/lib/plat/unix/unix-service.c\n@@ -71,6 +71,9 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)\n \tvolatile struct lws_context_per_thread *vpt;\n \tstruct lws_context_per_thread *pt;\n \tlws_usec_t timeout_us, us;\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_usec_t a, b;\n+#endif\n \tint n;\n #if (defined(LWS_ROLE_WS) \u0026\u0026 !defined(LWS_WITHOUT_EXTENSIONS)) || defined(LWS_WITH_TLS)\n \tint m;\n@@ -81,11 +84,14 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)\n \tif (!context)\n \t\treturn 1;\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tb \u003d\n+#endif\n+\t\t\tus \u003d lws_now_usecs();\n+\n \tpt \u003d \u0026context-\u003ept[tsi];\n \tvpt \u003d (volatile struct lws_context_per_thread *)pt;\n \n-\tlws_stats_bump(pt, LWSSTATS_C_SERVICE_ENTRY, 1);\n-\n \tif (timeout_ms \u003c 0)\n \t\ttimeout_ms \u003d 0;\n \telse\n@@ -108,7 +114,6 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)\n \t\tpt-\u003eservice_tid_detected \u003d 1;\n \t}\n \n-\tus \u003d lws_now_usecs();\n \tlws_pt_lock(pt, __func__);\n \t/*\n \t * service ripe scheduled events, and limit wait to next expected one\n@@ -144,15 +149,9 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)\n \tvpt-\u003einside_poll \u003d 0;\n \tlws_memory_barrier();\n \n-\t#if defined(LWS_WITH_DETAILED_LATENCY)\n-\t/*\n-\t * so we can track how long it took before we actually read a\n-\t * POLLIN that was signalled when we last exited poll()\n-\t */\n-\tif (context-\u003edetailed_latency_cb)\n-\t\tpt-\u003eust_left_poll \u003d lws_now_usecs();\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tb \u003d lws_now_usecs();\n #endif\n-\n \t/* Collision will be rare and brief. Spin until it completes */\n \twhile (vpt-\u003eforeign_spinlock)\n \t\t;\n@@ -207,14 +206,16 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)\n #if (defined(LWS_ROLE_WS) \u0026\u0026 !defined(LWS_WITHOUT_EXTENSIONS)) || defined(LWS_WITH_TLS)\n \t\t!m \u0026\u0026\n #endif\n-\t\t!n) { /* nothing to do */\n+\t\t!n) /* nothing to do */\n \t\tlws_service_do_ripe_rxflow(pt);\n+\telse\n+\t\tif (_lws_plat_service_forced_tsi(context, tsi) \u003c 0)\n+\t\t\treturn -1;\n \n-\t\treturn 0;\n-\t}\n-\n-\tif (_lws_plat_service_forced_tsi(context, tsi) \u003c 0)\n-\t\treturn -1;\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_metric_event(context-\u003emt_service, METRES_GO,\n+\t\t\t (u_mt_t) (a + (lws_now_usecs() - b)));\n+#endif\n \n \tif (pt-\u003edestroy_self) {\n \t\tlws_context_destroy(pt-\u003econtext);\ndiff --git a/lib/roles/h1/ops-h1.c b/lib/roles/h1/ops-h1.c\nindex ea69163..a6e53e4 100644\n--- a/lib/roles/h1/ops-h1.c\n+++ b/lib/roles/h1/ops-h1.c\n@@ -521,19 +521,6 @@ try_pollout:\n \t\t\treturn LWS_HPI_RET_HANDLED;\n \t\t}\n \n-\t\tlws_stats_bump(pt, LWSSTATS_C_WRITEABLE_CB, 1);\n-#if defined(LWS_WITH_STATS)\n-\t\tif (wsi-\u003eactive_writable_req_us) {\n-\t\t\tuint64_t ul \u003d lws_now_usecs() -\n-\t\t\t\t\twsi-\u003eactive_writable_req_us;\n-\n-\t\t\tlws_stats_bump(pt, LWSSTATS_US_WRITABLE_DELAY_AVG, ul);\n-\t\t\tlws_stats_max(pt,\n-\t\t\t\t LWSSTATS_US_WORST_WRITABLE_DELAY, ul);\n-\t\t\twsi-\u003eactive_writable_req_us \u003d 0;\n-\t\t}\n-#endif\n-\n \t\tn \u003d user_callback_handle_rxflow(wsi-\u003ea.protocol-\u003ecallback, wsi,\n \t\t\t\t\t\tLWS_CALLBACK_HTTP_WRITEABLE,\n \t\t\t\t\t\twsi-\u003euser_space, NULL, 0);\n@@ -934,6 +921,7 @@ rops_adoption_bind_h1(struct lws *wsi, int type, const char *vh_prot_name)\n #if defined(LWS_WITH_HTTP2)\n \tif ((!(type \u0026 LWS_ADOPT_ALLOW_SSL)) \u0026\u0026 (wsi-\u003ea.vhost-\u003eoptions \u0026 LWS_SERVER_OPTION_H2_PRIOR_KNOWLEDGE)) {\n \t\tlwsl_info(\u0022http/2 prior knowledge\u005cn\u0022);\n+\t\tlws_metrics_tag_wsi_add(wsi, \u0022upg\u0022, \u0022h2_prior\u0022);\n \t\tlws_role_call_alpn_negotiated(wsi, \u0022h2\u0022);\n \t}\n \telse\ndiff --git a/lib/roles/h2/hpack.c b/lib/roles/h2/hpack.c\nindex 326f9dd..68629e6 100644\n--- a/lib/roles/h2/hpack.c\n+++ b/lib/roles/h2/hpack.c\n@@ -1422,7 +1422,7 @@ int lws_add_http2_header_by_name(struct lws *wsi, const unsigned char *name,\n \n \t*((*p)++) \u003d 0; /* literal hdr, literal name, */\n \n-\t*((*p)++) \u003d 0 | (uint8_t)lws_h2_num_start(7, (unsigned long)len); /* non-HUF */\n+\t*((*p)++) \u003d (uint8_t)(0 | (uint8_t)lws_h2_num_start(7, (unsigned long)len)); /* non-HUF */\n \tif (lws_h2_num(7, (unsigned long)len, p, end))\n \t\treturn 1;\n \n@@ -1432,7 +1432,7 @@ int lws_add_http2_header_by_name(struct lws *wsi, const unsigned char *name,\n \twhile(len--)\n \t\t*((*p)++) \u003d (uint8_t)tolower((int)*name++);\n \n-\t*((*p)++) \u003d 0 | (uint8_t)lws_h2_num_start(7, (unsigned long)length); /* non-HUF */\n+\t*((*p)++) \u003d (uint8_t)(0 | (uint8_t)lws_h2_num_start(7, (unsigned long)length)); /* non-HUF */\n \tif (lws_h2_num(7, (unsigned long)length, p, end))\n \t\treturn 1;\n \ndiff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c\nindex 07d0ef6..ba836e9 100644\n--- a/lib/roles/h2/http2.c\n+++ b/lib/roles/h2/http2.c\n@@ -261,6 +261,11 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,\n \t\treturn NULL;\n \t}\n \n+#if defined(LWS_WITH_SERVER)\n+\tif (lwsi_role_server(parent_wsi))\n+\t\tlws_metrics_caliper_bind(wsi-\u003ecal_conn, wsi-\u003ea.context-\u003emth_srv);\n+#endif\n+\n \th2n-\u003ehighest_sid_opened \u003d sid;\n \n \tlws_wsi_mux_insert(wsi, parent_wsi, sid);\n@@ -281,10 +286,6 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,\n \tif (lws_ensure_user_space(wsi))\n \t\tgoto bail1;\n \n-#if defined(LWS_WITH_SERVER_STATUS)\n-\twsi-\u003ea.vhost-\u003econn_stats.h2_subs++;\n-#endif\n-\n #if defined(LWS_WITH_SERVER) \u0026\u0026 defined(LWS_WITH_SECURE_STREAMS)\n \tif (lws_adopt_ss_server_accept(wsi))\n \t\tgoto bail1;\n@@ -362,10 +363,6 @@ lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi)\n \n \tlws_callback_on_writable(wsi);\n \n-#if defined(LWS_WITH_SERVER_STATUS)\n-\twsi-\u003ea.vhost-\u003econn_stats.h2_subs++;\n-#endif\n-\n \treturn wsi;\n \n bail1:\n@@ -818,9 +815,6 @@ int lws_h2_do_pps_send(struct lws *wsi)\n \t\t\th2n-\u003eswsi-\u003eh2.END_STREAM \u003d 1;\n \t\t\tlwsl_info(\u0022servicing initial http request\u005cn\u0022);\n \n-#if defined(LWS_WITH_SERVER_STATUS)\n-\t\t\twsi-\u003ea.vhost-\u003econn_stats.h2_trans++;\n-#endif\n #if defined(LWS_WITH_SERVER)\n \t\t\tif (lws_http_action(h2n-\u003eswsi))\n \t\t\t\tgoto bail;\n@@ -1713,9 +1707,6 @@ lws_h2_parse_end_of_frame(struct lws *wsi)\n \t\tlws_http_compression_validate(h2n-\u003eswsi);\n #endif\n \n-#if defined(LWS_WITH_SERVER_STATUS)\n-\t\twsi-\u003ea.vhost-\u003econn_stats.h2_trans++;\n-#endif\n \t\tp \u003d lws_hdr_simple_ptr(h2n-\u003eswsi, WSI_TOKEN_HTTP_COLON_METHOD);\n \t\t/*\n \t\t * duplicate :path into the individual method uri header\n@@ -2532,10 +2523,6 @@ lws_h2_client_handshake(struct lws *wsi)\n \tif (lws_finalize_http_header(wsi, \u0026p, end))\n \t\tgoto fail_length;\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\twsi-\u003edetlat.earliest_write_req_pre_write \u003d lws_now_usecs();\n-#endif\n-\n \tm \u003d LWS_WRITE_HTTP_HEADERS;\n #if defined(LWS_WITH_CLIENT)\n \t/* below is not needed in spec, indeed it destroys the long poll\ndiff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c\nindex d5a2eba..f46cfcc 100644\n--- a/lib/roles/h2/ops-h2.c\n+++ b/lib/roles/h2/ops-h2.c\n@@ -521,13 +521,12 @@ rops_check_upgrades_h2(struct lws *wsi)\n \tif (!p || strcmp(p, \u0022websocket\u0022))\n \t\treturn LWS_UPG_RET_CONTINUE;\n \n-#if defined(LWS_WITH_SERVER_STATUS)\n-\twsi-\u003ea.vhost-\u003econn_stats.ws_upg++;\n-#endif\n \tlwsl_info(\u0022Upgrade h2 to ws\u005cn\u0022);\n \tlws_mux_mark_immortal(wsi);\n \twsi-\u003eh2_stream_carries_ws \u003d 1;\n \n+\tlws_metrics_tag_wsi_add(wsi, \u0022upg\u0022, \u0022ws_over_h2\u0022);\n+\n \tif (lws_process_ws_upgrade(wsi))\n \t\treturn LWS_UPG_RET_BAIL;\n \n@@ -1254,9 +1253,6 @@ rops_alpn_negotiated_h2(struct lws *wsi, const char *alpn)\n #endif\n \n \twsi-\u003eupgraded_to_http2 \u003d 1;\n-#if defined(LWS_WITH_SERVER_STATUS)\n-\twsi-\u003ea.vhost-\u003econn_stats.h2_alpn++;\n-#endif\n \n \t/* adopt the header info */\n \ndiff --git a/lib/roles/http/client/client-http.c b/lib/roles/http/client/client-http.c\nindex dfb2105..1e6da79 100644\n--- a/lib/roles/http/client/client-http.c\n+++ b/lib/roles/http/client/client-http.c\n@@ -67,7 +67,10 @@ lws_http_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd)\n \t\t * timeout protection set in client-handshake.c\n \t\t */\n \t\tif (pollfd-\u003erevents \u0026 LWS_POLLOUT)\n-\t\t\tlws_client_connect_3_connect(wsi, NULL, NULL, 0, NULL);\n+\t\t\tif (lws_client_connect_3_connect(wsi, NULL, NULL, 0, NULL) \u003d\u003d NULL) {\n+\t\t\t\tlwsl_client(\u0022closed\u005cn\u0022);\n+\t\t\t\treturn -1;\n+\t\t\t}\n \t\tbreak;\n \n #if defined(LWS_WITH_SOCKS5)\n@@ -203,16 +206,6 @@ start_ws_handshake:\n \t\t\t}\n \t\t}\n #endif\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\t\tif (context-\u003edetailed_latency_cb) {\n-\t\t\twsi-\u003edetlat.type \u003d LDLT_TLS_NEG_CLIENT;\n-\t\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] \u003d\n-\t\t\t\t(uint32_t)(lws_now_usecs() -\n-\t\t\t\twsi-\u003edetlat.earliest_write_req_pre_write);\n-\t\t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d 0;\n-\t\t\tlws_det_lat_cb(wsi-\u003ea.context, \u0026wsi-\u003edetlat);\n-\t\t}\n-#endif\n \n #if defined (LWS_WITH_HTTP2)\n \t\tif (wsi-\u003eclient_h2_alpn \u0026\u0026 lwsi_state(wsi) !\u003d LRS_H1C_ISSUE_HANDSHAKE2) {\n@@ -271,9 +264,7 @@ hs2:\n \t\t\t \u0022(wsistate 0x%lx), w sock %d\u005cn\u0022,\n \t\t\t __func__, lws_wsi_tag(wsi),\n \t\t\t (unsigned long)wsi-\u003ewsistate, wsi-\u003edesc.sockfd);\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\t\twsi-\u003edetlat.earliest_write_req_pre_write \u003d lws_now_usecs();\n-#endif\n+\n \t\tn \u003d lws_ssl_capable_write(wsi, (unsigned char *)sb, lws_ptr_diff_size_t(p, sb));\n \t\tswitch (n) {\n \t\tcase LWS_SSL_CAPABLE_ERROR:\n@@ -485,6 +476,10 @@ lws_http_transaction_completed_client(struct lws *wsi)\n \tlwsl_info(\u0022%s: %s (%s)\u005cn\u0022, __func__, lws_wsi_tag(wsi),\n \t\t\twsi-\u003ea.protocol-\u003ename);\n \n+\t// if (wsi-\u003ehttp.ah \u0026\u0026 wsi-\u003ehttp.ah-\u003ehttp_response)\n+\t/* we're only judging if any (200, or 500 etc) http txn completed */\n+\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_GO);\n+\n \tif (user_callback_handle_rxflow(wsi-\u003ea.protocol-\u003ecallback, wsi,\n \t\t\t\t\tLWS_CALLBACK_COMPLETED_CLIENT_HTTP,\n \t\t\t\t\twsi-\u003euser_space, NULL, 0)) {\n@@ -1210,6 +1205,8 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt)\n \tif (wsi-\u003eclient_http_body_pending)\n \t\tlws_callback_on_writable(wsi);\n \n+\tlws_metrics_caliper_bind(wsi-\u003ecal_conn, wsi-\u003ea.context-\u003emt_http_txn);\n+\n \t// puts(pkt);\n \n \treturn p;\ndiff --git a/lib/roles/http/header.c b/lib/roles/http/header.c\nindex eb84d7c..83983a5 100644\n--- a/lib/roles/http/header.c\n+++ b/lib/roles/http/header.c\n@@ -125,9 +125,6 @@ lws_finalize_write_http_header(struct lws *wsi, unsigned char *start,\n \tp \u003d *pp;\n \tlen \u003d lws_ptr_diff(p, start);\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\twsi-\u003edetlat.earliest_write_req_pre_write \u003d lws_now_usecs();\n-#endif\n \tif (lws_write(wsi, start, (unsigned int)len, LWS_WRITE_HTTP_HEADERS) !\u003d len)\n \t\treturn 1;\n \n@@ -316,6 +313,7 @@ lws_add_http_header_status(struct lws *wsi, unsigned int _code,\n \tunsigned char code_and_desc[60];\n \tint n;\n \n+\twsi-\u003ehttp.response_code \u003d code;\n #ifdef LWS_WITH_ACCESS_LOG\n \twsi-\u003ehttp.access_log.response \u003d (int)code;\n #endif\n@@ -482,9 +480,6 @@ lws_return_http_status(struct lws *wsi, unsigned int code,\n \t\t *\n \t\t * Solve it by writing the headers now...\n \t\t */\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\t\twsi-\u003edetlat.earliest_write_req_pre_write \u003d lws_now_usecs();\n-#endif\n \t\tm \u003d lws_write(wsi, start, lws_ptr_diff_size_t(p, start),\n \t\t\t LWS_WRITE_HTTP_HEADERS);\n \t\tif (m !\u003d lws_ptr_diff(p, start))\ndiff --git a/lib/roles/http/parsers.c b/lib/roles/http/parsers.c\nindex 778d96a..2999faa 100644\n--- a/lib/roles/http/parsers.c\n+++ b/lib/roles/http/parsers.c\n@@ -236,11 +236,8 @@ lws_header_table_attach(struct lws *wsi, int autoservice)\n \n \tn \u003d pt-\u003ehttp.ah_count_in_use \u003d\u003d (int)context-\u003emax_http_header_pool;\n #if defined(LWS_WITH_PEER_LIMITS)\n-\tif (!n) {\n+\tif (!n)\n \t\tn \u003d lws_peer_confirm_ah_attach_ok(context, wsi-\u003epeer);\n-\t\tif (n)\n-\t\t\tlws_stats_bump(pt, LWSSTATS_C_PEER_LIMIT_AH_DENIED, 1);\n-\t}\n #endif\n \tif (n) {\n \t\t/*\n@@ -375,12 +372,7 @@ int __lws_header_table_detach(struct lws *wsi, int autoservice)\n \t\t\twsi \u003d *pwsi;\n \t\t\tpwsi_eligible \u003d pwsi;\n \t\t}\n-#if defined(LWS_WITH_PEER_LIMITS)\n-\t\telse\n-\t\t\tif (!(*pwsi)-\u003ehttp.ah_wait_list)\n-\t\t\t\tlws_stats_bump(pt,\n-\t\t\t\t\tLWSSTATS_C_PEER_LIMIT_AH_DENIED, 1);\n-#endif\n+\n \t\tpwsi \u003d \u0026(*pwsi)-\u003ehttp.ah_wait_list;\n \t}\n \ndiff --git a/lib/roles/http/private-lib-roles-http.h b/lib/roles/http/private-lib-roles-http.h\nindex d8d9fc1..94ee876 100644\n--- a/lib/roles/http/private-lib-roles-http.h\n+++ b/lib/roles/http/private-lib-roles-http.h\n@@ -248,6 +248,9 @@ struct _lws_http_mode_related {\n #ifdef LWS_WITH_ACCESS_LOG\n \tstruct lws_access_log access_log;\n #endif\n+#if defined(LWS_WITH_SERVER)\n+\tunsigned int response_code;\n+#endif\n #ifdef LWS_WITH_CGI\n \tstruct lws_cgi *cgi; /* wsi being cgi stream have one of these */\n #endif\ndiff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c\nindex c93d43c..c6b4bf6 100644\n--- a/lib/roles/http/server/server.c\n+++ b/lib/roles/http/server/server.c\n@@ -797,6 +797,10 @@ lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len)\n \t\t uri_ptr[hm-\u003emountpoint_len] \u003d\u003d '/' ||\n \t\t hm-\u003emountpoint_len \u003d\u003d 1)\n \t\t ) {\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t\tlws_metrics_tag_wsi_add(wsi, \u0022mnt\u0022, hm-\u003emountpoint);\n+#endif\n+\n \t\t\tif (hm-\u003eorigin_protocol \u003d\u003d LWSMPRO_CALLBACK ||\n \t\t\t ((hm-\u003eorigin_protocol \u003d\u003d LWSMPRO_CGI ||\n \t\t\t lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) ||\n@@ -1416,6 +1420,9 @@ lws_http_action(struct lws *wsi)\n \tif (meth \u003c 0 || meth \u003e\u003d (int)LWS_ARRAY_SIZE(method_names))\n \t\tgoto bail_nuke_ah;\n \n+\tlws_metrics_tag_wsi_add(wsi, \u0022vh\u0022, wsi-\u003ea.vhost-\u003ename);\n+\tlws_metrics_tag_wsi_add(wsi, \u0022meth\u0022, method_names[meth]);\n+\n \t/* we insist on absolute paths */\n \n \tif (!uri_ptr || uri_ptr[0] !\u003d '/') {\n@@ -2076,17 +2083,9 @@ raw_transition:\n \t\t} else\n \t\t\tlwsl_info(\u0022no host\u005cn\u0022);\n \n-\t\tif (!lwsi_role_h2(wsi) || !lwsi_role_server(wsi)) {\n-#if defined(LWS_WITH_SERVER_STATUS)\n-\t\t\twsi-\u003ea.vhost-\u003econn_stats.h1_trans++;\n-#endif\n-\t\t\tif (!wsi-\u003econn_stat_done) {\n-#if defined(LWS_WITH_SERVER_STATUS)\n-\t\t\t\twsi-\u003ea.vhost-\u003econn_stats.h1_conn++;\n-#endif\n-\t\t\t\twsi-\u003econn_stat_done \u003d 1;\n-\t\t\t}\n-\t\t}\n+\t\tif ((!lwsi_role_h2(wsi) || !lwsi_role_server(wsi)) \u0026\u0026\n+\t\t (!wsi-\u003econn_stat_done))\n+\t\t\twsi-\u003econn_stat_done \u003d 1;\n \n \t\t/* check for unwelcome guests */\n #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS)\n@@ -2122,9 +2121,6 @@ raw_transition:\n \n \t\t\t\t\t/* wsi close will do the log */\n #endif\n-#if defined(LWS_WITH_SERVER_STATUS)\n-\t\t\t\t\twsi-\u003ea.vhost-\u003econn_stats.rejected++;\n-#endif\n \t\t\t\t\t/*\n \t\t\t\t\t * We don't want anything from\n \t\t\t\t\t * this rejected guy. Follow\n@@ -2215,18 +2211,14 @@ raw_transition:\n \n \t\t\tif (!strcasecmp(up, \u0022websocket\u0022)) {\n #if defined(LWS_ROLE_WS)\n-#if defined(LWS_WITH_SERVER_STATUS)\n-\t\t\t\twsi-\u003ea.vhost-\u003econn_stats.ws_upg++;\n-#endif\n+\t\t\t\tlws_metrics_tag_wsi_add(wsi, \u0022upg\u0022, \u0022ws\u0022);\n \t\t\t\tlwsl_info(\u0022Upgrade to ws\u005cn\u0022);\n \t\t\t\tgoto upgrade_ws;\n #endif\n \t\t\t}\n #if defined(LWS_WITH_HTTP2)\n \t\t\tif (!strcasecmp(up, \u0022h2c\u0022)) {\n-#if defined(LWS_WITH_SERVER_STATUS)\n-\t\t\t\twsi-\u003ea.vhost-\u003econn_stats.h2_upg++;\n-#endif\n+\t\t\t\tlws_metrics_tag_wsi_add(wsi, \u0022upg\u0022, \u0022h2c\u0022);\n \t\t\t\tlwsl_info(\u0022Upgrade to h2c\u005cn\u0022);\n \t\t\t\tgoto upgrade_h2c;\n \t\t\t}\n@@ -2379,6 +2371,15 @@ lws_http_transaction_completed(struct lws *wsi)\n \t\treturn 0;\n \t}\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t{\n+\t\tchar tmp[10];\n+\n+\t\tlws_snprintf(tmp, sizeof(tmp), \u0022%u\u0022, wsi-\u003ehttp.response_code);\n+\t\tlws_metrics_tag_wsi_add(wsi, \u0022status\u0022, tmp);\n+\t}\n+#endif\n+\n \tlwsl_info(\u0022%s: %s\u005cn\u0022, __func__, lws_wsi_tag(wsi));\n \n #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)\ndiff --git a/lib/roles/mqtt/client/client-mqtt.c b/lib/roles/mqtt/client/client-mqtt.c\nindex 8e87218..93b539e 100644\n--- a/lib/roles/mqtt/client/client-mqtt.c\n+++ b/lib/roles/mqtt/client/client-mqtt.c\n@@ -264,17 +264,6 @@ lws_mqtt_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd,\n \t\t\twsi-\u003etls.ssl \u003d NULL;\n #endif /* LWS_WITH_TLS */\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\t\tif (context-\u003edetailed_latency_cb) {\n-\t\t\twsi-\u003edetlat.type \u003d LDLT_TLS_NEG_CLIENT;\n-\t\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] \u003d\n-\t\t\t\t(uint32_t)(lws_now_usecs() -\n-\t\t\t\twsi-\u003edetlat.earliest_write_req_pre_write);\n-\t\t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d 0;\n-\t\t\tlws_det_lat_cb(wsi-\u003ea.context, \u0026wsi-\u003edetlat);\n-\t\t}\n-#endif\n-\n \t\t/* fallthru */\n \n #if defined(LWS_WITH_SOCKS5)\ndiff --git a/lib/roles/mqtt/mqtt.c b/lib/roles/mqtt/mqtt.c\nindex b47bbe9..02a6482 100644\n--- a/lib/roles/mqtt/mqtt.c\n+++ b/lib/roles/mqtt/mqtt.c\n@@ -992,7 +992,7 @@ cmd_completion:\n \t\t\t\tlws_set_timeout(wsi, 0, 0);\n \n \t\t\t\tw \u003d lws_create_new_server_wsi(wsi-\u003ea.vhost,\n-\t\t\t\t\t\t\t wsi-\u003etsi, \u0022mqtt\u0022);\n+\t\t\t\t\t\t\t wsi-\u003etsi, \u0022mqtt_sid1\u0022);\n \t\t\t\tif (!w) {\n \t\t\t\t\tlwsl_notice(\u0022%s: sid 1 migrate failed\u005cn\u0022,\n \t\t\t\t\t\t\t__func__);\n@@ -1042,10 +1042,6 @@ cmd_completion:\n \t\t\t\t\t\t__func__, lws_wsi_tag(wsi),\n \t\t\t\t\t\tlws_wsi_tag(w));\n \n-\t\t\t#if defined(LWS_WITH_SERVER_STATUS)\n-\t\t\t\twsi-\u003ea.vhost-\u003econn_stats.h2_subs++;\n-\t\t\t#endif\n-\n \t\t\t\t/*\n \t\t\t\t * It was the last thing we were waiting for\n \t\t\t\t * before we can be fully ESTABLISHED\n@@ -2107,10 +2103,6 @@ lws_wsi_mqtt_adopt(struct lws *parent_wsi, struct lws *wsi)\n \tlws_mqtt_set_client_established(wsi);\n \tlws_callback_on_writable(wsi);\n \n-#if defined(LWS_WITH_SERVER_STATUS)\n-\twsi-\u003ea.vhost-\u003econn_stats.mqtt_subs++;\n-#endif\n-\n \treturn wsi;\n \n bail1:\ndiff --git a/lib/roles/raw-skt/ops-raw-skt.c b/lib/roles/raw-skt/ops-raw-skt.c\nindex 229f5af..cbe927c 100644\n--- a/lib/roles/raw-skt/ops-raw-skt.c\n+++ b/lib/roles/raw-skt/ops-raw-skt.c\n@@ -120,6 +120,8 @@ rops_handle_POLLIN_raw_skt(struct lws_context_per_thread *pt, struct lws *wsi,\n \t\t\tbuffered \u003d lws_buflist_aware_read(pt, wsi, \u0026ebuf, 1, __func__);\n \t\t\tswitch (ebuf.len) {\n \t\t\tcase 0:\n+\t\t\t\tif (wsi-\u003eunix_skt)\n+\t\t\t\t\tbreak;\n \t\t\t\tlwsl_info(\u0022%s: read 0 len\u005cn\u0022, __func__);\n \t\t\t\twsi-\u003eseen_zero_length_recv \u003d 1;\n \t\t\t\tif (lws_change_pollfd(wsi, LWS_POLLIN, 0))\n@@ -202,18 +204,6 @@ try_pollout:\n \t/* clear back-to-back write detection */\n \twsi-\u003ecould_have_pending \u003d 0;\n \n-\tlws_stats_bump(pt, LWSSTATS_C_WRITEABLE_CB, 1);\n-#if defined(LWS_WITH_STATS)\n-\tif (wsi-\u003eactive_writable_req_us) {\n-\t\tuint64_t ul \u003d lws_now_usecs() -\n-\t\t\t\twsi-\u003eactive_writable_req_us;\n-\n-\t\tlws_stats_bump(pt, LWSSTATS_US_WRITABLE_DELAY_AVG, ul);\n-\t\tlws_stats_max(pt,\n-\t\t\t LWSSTATS_US_WORST_WRITABLE_DELAY, ul);\n-\t\twsi-\u003eactive_writable_req_us \u003d 0;\n-\t}\n-#endif\n \tn \u003d user_callback_handle_rxflow(wsi-\u003ea.protocol-\u003ecallback,\n \t\t\twsi, LWS_CALLBACK_RAW_WRITEABLE,\n \t\t\twsi-\u003euser_space, NULL, 0);\ndiff --git a/lib/roles/ws/client-ws.c b/lib/roles/ws/client-ws.c\nindex af733cc..7a795b6 100644\n--- a/lib/roles/ws/client-ws.c\n+++ b/lib/roles/ws/client-ws.c\n@@ -251,11 +251,6 @@ lws_client_ws_upgrade(struct lws *wsi, const char **cce)\n \tchar ignore;\n #endif\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\t\twsi-\u003edetlat.earliest_write_req \u003d 0;\n-\t\twsi-\u003edetlat.earliest_write_req_pre_write \u003d 0;\n-#endif\n-\n \tif (wsi-\u003eclient_mux_substream) {/* !!! client ws-over-h2 not there yet */\n \t\tlwsl_warn(\u0022%s: client ws-over-h2 upgrade not supported yet\u005cn\u0022,\n \t\t\t __func__);\ndiff --git a/lib/secure-streams/policy-common.c b/lib/secure-streams/policy-common.c\nindex 14de047..18c05dd 100644\n--- a/lib/secure-streams/policy-common.c\n+++ b/lib/secure-streams/policy-common.c\n@@ -297,6 +297,17 @@ lws_ss_policy_set(struct lws_context *context, const char *name)\n \tif (context-\u003eac_policy) {\n \t\tint n;\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tlws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,\n+\t\t\t\t\t context-\u003eowner_mtr_dynpol.head) {\n+\t\t\tlws_metric_policy_dyn_t *dm \u003d\n+\t\t\t\tlws_container_of(d, lws_metric_policy_dyn_t, list);\n+\n+\t\t\tlws_metric_policy_dyn_destroy(dm, 1); /* keep */\n+\n+\t\t} lws_end_foreach_dll_safe(d, d1);\n+#endif\n+\n \t\t/*\n \t\t * any existing ss created with the old policy have to go away\n \t\t * now, since they point to the shortly-to-be-destroyed old\n@@ -431,10 +442,27 @@ lws_ss_policy_set(struct lws_context *context, const char *name)\n \t\tx \u003d x-\u003enext;\n \t}\n \n+\tcontext-\u003elast_policy \u003d time(NULL);\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tif (context-\u003epss_policies)\n+\t\t((lws_ss_policy_t *)context-\u003epss_policies)-\u003emetrics \u003d\n+\t\t\t\t\t\targs-\u003eheads[LTY_METRICS].m;\n+#endif\n+\n \t/* and we can discard the parsing args object now, invalidating args */\n \n \tlws_free_set_NULL(context-\u003epol_args);\n #endif\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_metric_rebind_policies(context);\n+#endif\n+\n+#if defined(LWS_WITH_SYS_SMD)\n+\t(void)lws_smd_msg_printf(context, LWSSMDCL_SYSTEM_STATE,\n+\t\t\t\t \u0022{\u005c\u0022policy\u005c\u0022:\u005c\u0022updated\u005c\u0022,\u005c\u0022ts\u005c\u0022:%lu}\u0022,\n+\t\t\t\t (long)context-\u003elast_policy);\n+#endif\n+\n \treturn ret;\n }\ndiff --git a/lib/secure-streams/policy-json.c b/lib/secure-streams/policy-json.c\nindex d4e3bb3..3876ae2 100644\n--- a/lib/secure-streams/policy-json.c\n+++ b/lib/secure-streams/policy-json.c\n@@ -41,6 +41,11 @@ static const char * const lejp_tokens_policy[] \u003d {\n \t\u0022certs[].*\u0022,\n \t\u0022trust_stores[].name\u0022,\n \t\u0022trust_stores[].stack\u0022,\n+\t\u0022metrics[].name\u0022,\n+\t\u0022metrics[].us_schedule\u0022,\n+\t\u0022metrics[].us_halflife\u0022,\n+\t\u0022metrics[].min_outlier\u0022,\n+\t\u0022metrics[].report\u0022,\n \t\u0022s[].*.endpoint\u0022,\n \t\u0022s[].*.via-socks5\u0022,\n \t\u0022s[].*.protocol\u0022,\n@@ -135,6 +140,11 @@ typedef enum {\n \tLSSPPT_CERTS,\n \tLSSPPT_TRUST_STORES_NAME,\n \tLSSPPT_TRUST_STORES_STACK,\n+\tLSSPPT_METRICS_NAME,\n+\tLSSPPT_METRICS_US_SCHEDULE,\n+\tLSSPPT_METRICS_US_HALFLIFE,\n+\tLSSPPT_METRICS_MIN_OUTLIER,\n+\tLSSPPT_METRICS_REPORT,\n \tLSSPPT_ENDPOINT,\n \tLSSPPT_VH_VIA_SOCKS5,\n \tLSSPPT_PROTOCOL,\n@@ -225,6 +235,7 @@ static uint16_t sizes[] \u003d {\n \tsizeof(lws_ss_trust_store_t),\n \tsizeof(lws_ss_policy_t),\n \tsizeof(lws_ss_auth_t),\n+\tsizeof(lws_metric_policy_t),\n };\n \n static const char * const protonames[] \u003d {\n@@ -253,6 +264,25 @@ lws_ss_policy_find_auth_by_name(struct policy_cb_args *a,\n \treturn NULL;\n }\n \n+static int\n+lws_ss_policy_alloc_helper(struct policy_cb_args *a, int type)\n+{\n+\t/*\n+\t * We do the pointers always as .b union member, all of the\n+\t * participating structs begin with .next and .name the same\n+\t */\n+\n+\ta-\u003ecurr[type].b \u003d lwsac_use_zero(\u0026a-\u003eac,\n+\t\t\t\tsizes[type], POL_AC_GRAIN);\n+\tif (!a-\u003ecurr[type].b)\n+\t\treturn 1;\n+\n+\ta-\u003ecurr[type].b-\u003enext \u003d a-\u003eheads[type].b;\n+\ta-\u003eheads[type].b \u003d a-\u003ecurr[type].b;\n+\n+\treturn 0;\n+}\n+\n static signed char\n lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)\n {\n@@ -291,6 +321,13 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)\n \tcase LSSPPT_AUTH:\n \t\tn \u003d LTY_AUTH;\n \t\tbreak;\n+\tcase LSSPPT_METRICS_NAME:\n+\tcase LSSPPT_METRICS_US_SCHEDULE:\n+\tcase LSSPPT_METRICS_US_HALFLIFE:\n+\tcase LSSPPT_METRICS_MIN_OUTLIER:\n+\tcase LSSPPT_METRICS_REPORT:\n+\t\tn \u003d LTY_METRICS;\n+\t\tbreak;\n \t}\n \n \tif (reason \u003d\u003d LEJPCB_ARRAY_START \u0026\u0026\n@@ -300,13 +337,8 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)\n \t\ta-\u003ecount \u003d 0;\n \n \tif (reason \u003d\u003d LEJPCB_OBJECT_START \u0026\u0026 n \u003d\u003d LTY_AUTH) {\n-\t\ta-\u003ecurr[n].b \u003d lwsac_use_zero(\u0026a-\u003eac, sizes[n], POL_AC_GRAIN);\n-\t\tif (!a-\u003ecurr[n].b)\n+\t\tif (lws_ss_policy_alloc_helper(a, LTY_AUTH))\n \t\t\tgoto oom;\n-\n-\t\ta-\u003ecurr[n].b-\u003enext \u003d a-\u003eheads[n].b;\n-\t\ta-\u003eheads[n].b \u003d a-\u003ecurr[n].b;\n-\n \t\treturn 0;\n \t}\n \n@@ -360,7 +392,7 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)\n \t}\n \n \tif (reason \u003d\u003d LEJPCB_PAIR_NAME \u0026\u0026 n !\u003d -1 \u0026\u0026\n-\t (n !\u003d LTY_TRUSTSTORE \u0026\u0026 n !\u003d LTY_AUTH)) {\n+\t (n !\u003d LTY_TRUSTSTORE \u0026\u0026 n !\u003d LTY_AUTH \u0026\u0026 n !\u003d LTY_METRICS)) {\n \n \t\tp2 \u003d NULL;\n \t\tif (n \u003d\u003d LTY_POLICY) {\n@@ -490,18 +522,10 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)\n \t\tbreak;\n \n \tcase LSSPPT_TRUST_STORES_NAME:\n-\t\t/*\n-\t\t * We do the pointers always as .b, all of the participating\n-\t\t * structs begin with .next and .name\n-\t\t */\n-\t\ta-\u003ecurr[LTY_TRUSTSTORE].b \u003d lwsac_use_zero(\u0026a-\u003eac,\n-\t\t\t\t\tsizes[LTY_TRUSTSTORE], POL_AC_GRAIN);\n-\t\tif (!a-\u003ecurr[LTY_TRUSTSTORE].b)\n+\t\tif (lws_ss_policy_alloc_helper(a, LTY_TRUSTSTORE))\n \t\t\tgoto oom;\n \n \t\ta-\u003ecount \u003d 0;\n-\t\ta-\u003ecurr[LTY_TRUSTSTORE].b-\u003enext \u003d a-\u003eheads[LTY_TRUSTSTORE].b;\n-\t\ta-\u003eheads[LTY_TRUSTSTORE].b \u003d a-\u003ecurr[LTY_TRUSTSTORE].b;\n \t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_TRUSTSTORE].b-\u003ename;\n \n \t\tgoto string2;\n@@ -528,6 +552,31 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)\n \t\tlwsl_err(\u0022%s: unknown trust store entry %s\u005cn\u0022, __func__,\n \t\t\t dotstar);\n \t\tgoto oom;\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tcase LSSPPT_METRICS_NAME:\n+\t\tif (lws_ss_policy_alloc_helper(a, LTY_METRICS))\n+\t\t\tgoto oom;\n+\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_METRICS].b-\u003ename;\n+\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_METRICS_US_SCHEDULE:\n+\t\ta-\u003ecurr[LTY_METRICS].m-\u003eus_schedule \u003d (uint32_t)atol(ctx-\u003ebuf);\n+\t\tbreak;\n+\n+\tcase LSSPPT_METRICS_US_HALFLIFE:\n+\t\ta-\u003ecurr[LTY_METRICS].m-\u003eus_decay_unit \u003d (uint32_t)atol(ctx-\u003ebuf);\n+\t\tbreak;\n+\n+\tcase LSSPPT_METRICS_MIN_OUTLIER:\n+\t\ta-\u003ecurr[LTY_METRICS].m-\u003emin_contributors \u003d (uint8_t)atoi(ctx-\u003ebuf);\n+\t\tbreak;\n+\n+\tcase LSSPPT_METRICS_REPORT:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_METRICS].m-\u003ereport;\n+\t\tgoto string2;\n+#endif\n \n \tcase LSSPPT_SERVER_CERT:\n \tcase LSSPPT_SERVER_KEY:\ndiff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h\nindex ab0b661..b386776 100644\n--- a/lib/secure-streams/private-lib-secure-streams.h\n+++ b/lib/secure-streams/private-lib-secure-streams.h\n@@ -49,6 +49,10 @@ typedef struct lws_ss_handle {\n \n \tlws_lifecycle_t\t\tlc;\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_metrics_caliper_compose(cal_txn)\n+#endif\n+\n \tstruct lws_dll2\t\tlist;\t /**\u003c pt lists active ss */\n \tstruct lws_dll2\t\tto_list; /**\u003c pt lists ss with pending to-s */\n #if defined(LWS_WITH_SERVER)\n@@ -290,6 +294,10 @@ typedef struct lws_sspc_handle {\n \tstruct lws_dll2\t\tclient_list;\n \tstruct lws_tx_credit\ttxc;\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_metrics_caliper_compose(cal_txn)\n+#endif\n+\n \tstruct lws\t\t*cwsi;\n \n \tstruct lws_dsh\t\t*dsh;\n@@ -333,6 +341,7 @@ union u {\n \tlws_ss_trust_store_t\t*t;\n \tlws_ss_policy_t\t\t*p;\n \tlws_ss_auth_t\t\t*a;\n+\tlws_metric_policy_t\t*m;\n };\n \n enum {\n@@ -341,6 +350,7 @@ enum {\n \tLTY_TRUSTSTORE,\n \tLTY_POLICY,\n \tLTY_AUTH,\n+\tLTY_METRICS,\n \n \t_LTY_COUNT /* always last */\n };\n@@ -510,6 +520,29 @@ struct ss_pcols {\n \tsecstream_protocol_get_txcr_t\t\t\ttx_cr_est;\n };\n \n+/*\n+ * Because both sides of the connection share the conn, we allocate it\n+ * during accepted adoption, and both sides point to it.\n+ *\n+ * When .ss or .wsi close, they must NULL their entry here so no dangling\n+ * refereneces.\n+ *\n+ * The last one of the accepted side and the onward side to close frees it.\n+ */\n+\n+\n+\n+\n+struct conn {\n+\tstruct lws_ss_serialization_parser parser;\n+\n+\tlws_dsh_t\t\t*dsh;\t/* unified buffer for both sides */\n+\tstruct lws\t\t*wsi;\t/* the proxy's client side */\n+\tlws_ss_handle_t\t\t*ss;\t/* the onward, ss side */\n+\n+\tlws_ss_conn_states_t\tstate;\n+};\n+\n extern const struct ss_pcols ss_pcol_h1;\n extern const struct ss_pcols ss_pcol_h2;\n extern const struct ss_pcols ss_pcol_ws;\ndiff --git a/lib/secure-streams/protocols/ss-h1.c b/lib/secure-streams/protocols/ss-h1.c\nindex 77c8971..0311dd8 100644\n--- a/lib/secure-streams/protocols/ss-h1.c\n+++ b/lib/secure-streams/protocols/ss-h1.c\n@@ -415,6 +415,7 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\t\tbreak;\n \t\t}\n \t\tassert(h-\u003epolicy);\n+\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, wsi);\n \t\tlwsl_info(\u0022%s: %s CLIENT_CONNECTION_ERROR: %s\u005cn\u0022, __func__,\n \t\t\t h-\u003elc.gutag, in ? (const char *)in : \u0022none\u0022);\n \t\t/* already disconnected, no action for DISCONNECT_ME */\n@@ -445,8 +446,11 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\t\tbreak;\n \n \t\tlws_sul_cancel(\u0026h-\u003esul_timeout);\n-\t\tlwsl_notice(\u0022%s: %s LWS_CALLBACK_CLOSED_CLIENT_HTTP\u005cn\u0022,\n-\t\t\t\t__func__, wsi-\u003elc.gutag);\n+\n+\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, wsi);\n+\t\t//lwsl_notice(\u0022%s: %s LWS_CALLBACK_CLOSED_CLIENT_HTTP\u005cn\u0022,\n+\t\t//\t\t__func__, wsi-\u003elc.gutag);\n+\n \t\th-\u003ewsi \u003d NULL;\n \n #if defined(LWS_WITH_SERVER)\n@@ -487,6 +491,13 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\t\t/* it's just telling use we connected / joined the nwsi */\n \t//\t\tbreak;\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tif (status) {\n+\t\t\tlws_snprintf((char *)buf, 10, \u0022%d\u0022, status);\n+\t\t\tlws_metrics_tag_ss_add(h, \u0022http_resp\u0022, (char *)buf);\n+\t\t}\n+#endif\n+\n \t\tif (status \u003d\u003d HTTP_STATUS_SERVICE_UNAVAILABLE /* 503 */ ||\n \t\t status \u003d\u003d 429 /* Too many requests */) {\n \t\t\t/*\n@@ -552,6 +563,7 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\tlws_sul_cancel(\u0026h-\u003esul);\n \n \t\tif (h-\u003eprev_ss_state !\u003d LWSSSCS_CONNECTED) {\n+\t\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, wsi);\n \t\t\tr \u003d lws_ss_event_helper(h, LWSSSCS_CONNECTED);\n \t\t\tif (r !\u003d LWSSSSRET_OK)\n \t\t\t\treturn _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, \u0026h);\n@@ -722,6 +734,12 @@ malformed:\n \t\t h-\u003ebeing_serialized \u0026\u0026 (\n \t\t\t\t!strcmp(h-\u003epolicy-\u003eu.http.method, \u0022PUT\u0022) ||\n \t\t\t\t!strcmp(h-\u003epolicy-\u003eu.http.method, \u0022POST\u0022))) {\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t\t/*\n+\t\t\t * If any hanging caliper measurement, dump it, and free any tags\n+\t\t\t */\n+\t\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n \t\t\tr \u003d lws_ss_event_helper(h, LWSSSCS_CONNECTED);\n \t\t\tif (r)\n \t\t\t\treturn _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, \u0026h);\n@@ -773,7 +791,7 @@ malformed:\n \t\treturn 0; /* don't passthru */\n \n \tcase LWS_CALLBACK_COMPLETED_CLIENT_HTTP:\n-\t\tlwsl_debug(\u0022%s: LWS_CALLBACK_COMPLETED_CLIENT_HTTP\u005cn\u0022, __func__);\n+\t\t// lwsl_debug(\u0022%s: LWS_CALLBACK_COMPLETED_CLIENT_HTTP\u005cn\u0022, __func__);\n \n \t\tif (!h)\n \t\t\treturn -1;\n@@ -919,7 +937,7 @@ malformed:\n \t\t}\n \n #if defined(LWS_WITH_SERVER)\n-\t\tif (!(h-\u003einfo.flags \u0026 LWSSSINFLAGS_ACCEPTED) \u0026\u0026\n+\t\tif ((h-\u003einfo.flags \u0026 LWSSSINFLAGS_ACCEPTED) /* server */ \u0026\u0026\n \t\t (f \u0026 LWSSS_FLAG_EOM) \u0026\u0026\n \t\t lws_http_transaction_completed(wsi))\n \t\t\treturn -1;\n@@ -973,6 +991,12 @@ malformed:\n \t\t}\n \n \t\tif (!h-\u003ess_dangling_connected) {\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t\t/*\n+\t\t\t * If any hanging caliper measurement, dump it, and free any tags\n+\t\t\t */\n+\t\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n \t\t\tr \u003d lws_ss_event_helper(h, LWSSSCS_CONNECTED);\n \t\t\tif (r)\n \t\t\t\treturn _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, \u0026h);\ndiff --git a/lib/secure-streams/protocols/ss-mqtt.c b/lib/secure-streams/protocols/ss-mqtt.c\nindex 1e1d1cd..0d719d5 100644\n--- a/lib/secure-streams/protocols/ss-mqtt.c\n+++ b/lib/secure-streams/protocols/ss-mqtt.c\n@@ -94,6 +94,12 @@ secstream_mqtt(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\th-\u003eretry \u003d 0;\n \t\th-\u003eseqstate \u003d SSSEQ_CONNECTED;\n \t\tlws_sul_cancel(\u0026h-\u003esul);\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t/*\n+\t\t * If any hanging caliper measurement, dump it, and free any tags\n+\t\t */\n+\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n \t\tr \u003d lws_ss_event_helper(h, LWSSSCS_CONNECTED);\n \t\tif (r !\u003d LWSSSSRET_OK)\n \t\t\treturn _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, \u0026h);\ndiff --git a/lib/secure-streams/protocols/ss-raw.c b/lib/secure-streams/protocols/ss-raw.c\nindex aa020a2..69d96bb 100644\n--- a/lib/secure-streams/protocols/ss-raw.c\n+++ b/lib/secure-streams/protocols/ss-raw.c\n@@ -60,8 +60,7 @@ secstream_raw(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\tif (!h)\n \t\t\tbreak;\n \t\tlws_sul_cancel(\u0026h-\u003esul_timeout);\n-\t\tlwsl_info(\u0022%s: %s, %s LWS_CALLBACK_CLOSED_CLIENT_HTTP\u005cn\u0022,\n-\t\t\t __func__, lws_ss_tag(h),\n+\t\tlwsl_info(\u0022%s: %s, %s RAW_CLOSE\u005cn\u0022, __func__, lws_ss_tag(h),\n \t\t\t h-\u003epolicy ? h-\u003epolicy-\u003estreamtype : \u0022no policy\u0022);\n \t\th-\u003ewsi \u003d NULL;\n #if defined(LWS_WITH_SERVER)\n@@ -69,6 +68,12 @@ secstream_raw(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\tlws_dll2_remove(\u0026h-\u003ecli_list);\n \t\tlws_pt_unlock(pt);\n #endif\n+\n+\t\t/* wsi is going down anyway */\n+\t\tr \u003d lws_ss_event_helper(h, LWSSSCS_DISCONNECTED);\n+\t\tif (r \u003d\u003d LWSSSSRET_DESTROY_ME)\n+\t\t\treturn _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, \u0026h);\n+\n \t\tif (h-\u003epolicy \u0026\u0026 !(h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_OPPORTUNISTIC) \u0026\u0026\n #if defined(LWS_WITH_SERVER)\n \t\t\t !(h-\u003einfo.flags \u0026 LWSSSINFLAGS_ACCEPTED) \u0026\u0026 /* not server */\n@@ -79,10 +84,7 @@ secstream_raw(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\t\t\treturn _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, \u0026h);\n \t\t\tbreak;\n \t\t}\n-\t\t/* wsi is going down anyway */\n-\t\tr \u003d lws_ss_event_helper(h, LWSSSCS_DISCONNECTED);\n-\t\tif (r \u003d\u003d LWSSSSRET_DESTROY_ME)\n-\t\t\treturn _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, \u0026h);\n+\n \t\tbreak;\n \n \tcase LWS_CALLBACK_RAW_CONNECTED:\n@@ -91,6 +93,12 @@ secstream_raw(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\th-\u003eretry \u003d 0;\n \t\th-\u003eseqstate \u003d SSSEQ_CONNECTED;\n \t\tlws_sul_cancel(\u0026h-\u003esul);\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t/*\n+\t\t * If any hanging caliper measurement, dump it, and free any tags\n+\t\t */\n+\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n \t\tr \u003d lws_ss_event_helper(h, LWSSSCS_CONNECTED);\n \t\tif (r !\u003d LWSSSSRET_OK)\n \t\t\treturn _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, \u0026h);\ndiff --git a/lib/secure-streams/protocols/ss-ws.c b/lib/secure-streams/protocols/ss-ws.c\nindex fb25a28..ad24c40 100644\n--- a/lib/secure-streams/protocols/ss-ws.c\n+++ b/lib/secure-streams/protocols/ss-ws.c\n@@ -106,6 +106,12 @@ secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\th-\u003eretry \u003d 0;\n \t\th-\u003eseqstate \u003d SSSEQ_CONNECTED;\n \t\tlws_sul_cancel(\u0026h-\u003esul);\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t/*\n+\t\t * If any hanging caliper measurement, dump it, and free any tags\n+\t\t */\n+\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n \t\tr \u003d lws_ss_event_helper(h, LWSSSCS_CONNECTED);\n \t\tif (r !\u003d LWSSSSRET_OK)\n \t\t\treturn _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, \u0026h);\ndiff --git a/lib/secure-streams/secure-streams-client.c b/lib/secure-streams/secure-streams-client.c\nindex e047755..21bcade 100644\n--- a/lib/secure-streams/secure-streams-client.c\n+++ b/lib/secure-streams/secure-streams-client.c\n@@ -71,9 +71,23 @@ lws_sspc_sul_retry_cb(lws_sorted_usec_list_t *sul)\n \ti.pwsi \u003d \u0026h-\u003ecwsi;\n \ti.opaque_user_data \u003d (void *)h;\n \ti.ssl_connection \u003d LCCSCF_SECSTREAM_PROXY_LINK;\n+\n+\tlws_metrics_caliper_bind(h-\u003ecal_txn, h-\u003econtext-\u003emt_ss_cliprox_conn);\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_metrics_tag_add(\u0026h-\u003ecal_txn.mtags_owner, \u0022ss\u0022, h-\u003essi.streamtype);\n+#endif\n+\n \t/* this wsi is the link to the proxy */\n \n \tif (!lws_client_connect_via_info(\u0026i)) {\n+\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t/*\n+\t\t * If any hanging caliper measurement, dump it, and free any tags\n+\t\t */\n+\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n+\n \t\tlws_sul_schedule(h-\u003econtext, 0, \u0026h-\u003esul_retry,\n \t\t\t\t lws_sspc_sul_retry_cb, LWS_US_PER_SEC);\n \n@@ -147,6 +161,12 @@ callback_sspc_client(struct lws *wsi, enum lws_callback_reasons reason,\n \n \tcase LWS_CALLBACK_CLIENT_CONNECTION_ERROR:\n \t\tlwsl_warn(\u0022%s: CONNECTION_ERROR\u005cn\u0022, __func__);\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t/*\n+\t\t * If any hanging caliper measurement, dump it, and free any tags\n+\t\t */\n+\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n \t\tlws_set_opaque_user_data(wsi, NULL);\n \t\th-\u003ecwsi \u003d NULL;\n \t\tlws_sul_schedule(h-\u003econtext, 0, \u0026h-\u003esul_retry,\n@@ -600,6 +620,13 @@ lws_sspc_destroy(lws_sspc_handle_t **ph)\n \n \th-\u003edestroying \u003d 1;\n \n+\t/* if this caliper is still dangling at destroy, we failed */\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t/*\n+\t * If any hanging caliper measurement, dump it, and free any tags\n+\t */\n+\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n \tif (h-\u003ess_dangling_connected \u0026\u0026 h-\u003essi.state) {\n \t\tlws_sspc_event_helper(h, LWSSSCS_DISCONNECTED, 0);\n \t\th-\u003ess_dangling_connected \u003d 0;\ndiff --git a/lib/secure-streams/secure-streams-process.c b/lib/secure-streams/secure-streams-process.c\nindex 306d11d..6bfa5d6 100644\n--- a/lib/secure-streams/secure-streams-process.c\n+++ b/lib/secure-streams/secure-streams-process.c\n@@ -51,26 +51,6 @@\n \n #include \u003cprivate-lib-core.h\u003e\n \n-/*\n- * Because both sides of the connection share the conn, we allocate it\n- * during accepted adoption, and both sides point to it.\n- *\n- * When .ss or .wsi close, they must NULL their entry here so no dangling\n- * refereneces.\n- *\n- * The last one of the accepted side and the onward side to close frees it.\n- */\n-\n-struct conn {\n-\tstruct lws_ss_serialization_parser parser;\n-\n-\tlws_dsh_t\t\t*dsh;\t/* unified buffer for both sides */\n-\tstruct lws\t\t*wsi;\t/* the proxy's client side */\n-\tlws_ss_handle_t\t\t*ss;\t/* the onward, ss side */\n-\n-\tlws_ss_conn_states_t\tstate;\n-};\n-\n struct raw_pss {\n \tstruct conn\t\t*conn;\n };\n@@ -313,9 +293,6 @@ callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason,\n \tlws_ss_metadata_t *md;\n \tlws_ss_info_t ssi;\n \tconst uint8_t *cp;\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tlws_usec_t us;\n-#endif\n \tchar s[256];\n \tuint8_t *p;\n \tsize_t si;\n@@ -588,7 +565,7 @@ callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason,\n \n \t\t\tcp \u003d p;\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n+#if 0\n \t\t\tif (cp[0] \u003d\u003d LWSSS_SER_RXPRE_RX_PAYLOAD \u0026\u0026\n \t\t\t wsi-\u003ea.context-\u003edetailed_latency_cb) {\n \ndiff --git a/lib/secure-streams/secure-streams-serialize.c b/lib/secure-streams/secure-streams-serialize.c\nindex a9e7469..0cbf088 100644\n--- a/lib/secure-streams/secure-streams-serialize.c\n+++ b/lib/secure-streams/secure-streams-serialize.c\n@@ -224,7 +224,7 @@ lws_ss_deserialize_tx_payload(struct lws_dsh *dsh, struct lws *wsi,\n \n \t*flags \u003d (int)lws_ser_ru32be(\u0026p[3]);\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n+#if 0\n \tif (wsi \u0026\u0026 wsi-\u003ea.context-\u003edetailed_latency_cb) {\n \t\t/*\n \t\t * use the proxied latency information to compute the client\n@@ -725,7 +725,7 @@ payload_ff:\n \t\t\t\t\t}\n \t\t\t\t}\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n+#if 0\n \t\t\t\tif (lws_det_lat_active(context)) {\n \t\t\t\t\tlws_detlat_t d;\n \n@@ -1233,6 +1233,13 @@ payload_ff:\n \t\t\t * CREATING now so we'll know the metadata to sync.\n \t\t\t */\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t\t/*\n+\t\t\t * If any hanging caliper measurement, dump it, and free any tags\n+\t\t\t */\n+\t\t\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n+\n \t\t\tif (!h-\u003ecreating_cb_done) {\n \t\t\t\tif (lws_ss_check_next_state(\u0026h-\u003elc, \u0026h-\u003eprev_ss_state,\n \t\t\t\t\t\t\t LWSSSCS_CREATING))\ndiff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c\nindex 1cc7e63..3d5f58f 100644\n--- a/lib/secure-streams/secure-streams.c\n+++ b/lib/secure-streams/secure-streams.c\n@@ -159,6 +159,7 @@ static const uint32_t ss_state_txn_validity[] \u003d {\n \t\t\t\t\t (1 \u003c\u003c LWSSSCS_POLL) |\n \t\t\t\t\t (1 \u003c\u003c LWSSSCS_TIMEOUT) |\n \t\t\t\t\t (1 \u003c\u003c LWSSSCS_DISCONNECTED) |\n+\t\t\t\t\t (1 \u003c\u003c LWSSSCS_UNREACHABLE) |\n \t\t\t\t\t (1 \u003c\u003c LWSSSCS_DESTROYING),\n \n \t[LWSSSCS_SERVER_TXN]\t\t\u003d (1 \u003c\u003c LWSSSCS_DISCONNECTED) |\n@@ -671,6 +672,14 @@ _lws_ss_client_connect(lws_ss_handle_t *h, int is_retry, void *conn_if_sspc_onw)\n \tlwsl_info(\u0022%s: connecting %s, '%s' '%s' %s\u005cn\u0022, __func__, i.method,\n \t\t\ti.alpn, i.address, i.path);\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t/* possibly already hanging connect retry... */\n+\tif (!h-\u003ecal_txn.mt)\n+\t\tlws_metrics_caliper_bind(h-\u003ecal_txn, h-\u003econtext-\u003emth_ss_conn);\n+\n+\tlws_metrics_tag_add(\u0026h-\u003ecal_txn.mtags_owner, \u0022ss\u0022, h-\u003epolicy-\u003estreamtype);\n+#endif\n+\n \th-\u003etxn_ok \u003d 0;\n \tr \u003d lws_ss_event_helper(h, LWSSSCS_CONNECTING);\n \tif (r) {\n@@ -1181,6 +1190,13 @@ lws_ss_destroy(lws_ss_handle_t **ppss)\n \tlws_fi_destroy(\u0026h-\u003efi);\n #endif\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t/*\n+\t * If any hanging caliper measurement, dump it, and free any tags\n+\t */\n+\tlws_metrics_caliper_report_hist(h-\u003ecal_txn, (struct lws *)NULL);\n+#endif\n+\n \tlws_sul_cancel(\u0026h-\u003esul_timeout);\n \n \t/* confirm no sul left scheduled in handle or user allocation object */\ndiff --git a/lib/system/CMakeLists.txt b/lib/system/CMakeLists.txt\nindex 9ad9d70..654264b 100644\n--- a/lib/system/CMakeLists.txt\n+++ b/lib/system/CMakeLists.txt\n@@ -63,6 +63,8 @@ if (LWS_WITH_NETWORK)\n \t\t\tsystem/fault-injection/fault-injection.c)\n \tendif()\n \n+\tadd_subdir_include_dirs(metrics)\n+\n endif()\n \n #\ndiff --git a/lib/system/async-dns/async-dns-parse.c b/lib/system/async-dns/async-dns-parse.c\nindex c0dd9ae..0014555 100644\n--- a/lib/system/async-dns/async-dns-parse.c\n+++ b/lib/system/async-dns/async-dns-parse.c\n@@ -1,7 +1,7 @@\n /*\n * libwebsockets - small server side websockets and web server implementation\n *\n- * Copyright (C) 2010 - 2019 Andy Green \u003candy@warmcat.com\u003e\n+ * Copyright (C) 2010 - 2021 Andy Green \u003candy@warmcat.com\u003e\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \u0022Software\u0022), to\n@@ -690,6 +690,8 @@ lws_adns_parse_udp(lws_async_dns_t *dns, const uint8_t *pkt, size_t len)\n \tc-\u003eincomplete \u003d 0;\n \tlws_async_dns_complete(q, q-\u003efirstcache);\n \n+\tq-\u003ego_nogo \u003d METRES_GO;\n+\n \t/*\n \t * the query is completely finished with\n \t */\ndiff --git a/lib/system/async-dns/async-dns.c b/lib/system/async-dns/async-dns.c\nindex 429d85a..ed13fd7 100644\n--- a/lib/system/async-dns/async-dns.c\n+++ b/lib/system/async-dns/async-dns.c\n@@ -34,6 +34,8 @@ static const lws_retry_bo_t retry_policy \u003d {\n void\n lws_adns_q_destroy(lws_adns_q_t *q)\n {\n+\tlws_metrics_caliper_report(q-\u003emetcal, (char)q-\u003ego_nogo);\n+\n \tlws_sul_cancel(\u0026q-\u003esul);\n \tlws_sul_cancel(\u0026q-\u003ewrite_sul);\n \tlws_dll2_remove(\u0026q-\u003elist);\n@@ -703,6 +705,10 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name,\n \t\tif (c-\u003eresults)\n \t\t\tc-\u003erefcount++;\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tlws_metric_event(context-\u003emt_adns_cache, METRES_GO, 0);\n+#endif\n+\n \t\tif (cb(wsi, name, c-\u003eresults, m, opaque) \u003d\u003d NULL)\n \t\t\treturn LADNS_RET_FAILED_WSI_CLOSED;\n \n@@ -710,6 +716,10 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name,\n \t} else\n \t\tlwsl_info(\u0022%s: %s uncached\u005cn\u0022, __func__, name);\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_metric_event(context-\u003emt_adns_cache, METRES_NOGO, 0);\n+#endif\n+\n \t/*\n \t * It's a 1.2.3.4 or ::1 type IP address already? We don't need a dns\n \t * server set up to be able to create an addrinfo result for that.\n@@ -876,6 +886,10 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name,\n \n \tlws_dll2_add_head(\u0026q-\u003elist, \u0026dns-\u003ewaiting);\n \n+\tlws_metrics_caliper_bind(q-\u003emetcal, context-\u003emt_conn_dns);\n+\tq-\u003ego_nogo \u003d METRES_NOGO;\n+\t/* caliper is reported in lws_adns_q_destroy */\n+\n \tlwsl_info(\u0022%s: created new query: %s\u005cn\u0022, __func__, name);\n \tlws_adns_dump(dns);\n \ndiff --git a/lib/system/async-dns/private-lib-async-dns.h b/lib/system/async-dns/private-lib-async-dns.h\nindex 969833b..f213448 100644\n--- a/lib/system/async-dns/private-lib-async-dns.h\n+++ b/lib/system/async-dns/private-lib-async-dns.h\n@@ -58,6 +58,8 @@ typedef struct {\n \tlws_sorted_usec_list_t\twrite_sul;\t/* fail if unable to write by this time */\n \tlws_dll2_t\t\tlist;\n \n+\tlws_metrics_caliper_compose(metcal)\n+\n \tlws_dll2_owner_t\twsi_adns;\n \tlws_async_dns_cb_t\tstandalone_cb;\t/* if not associated to wsi */\n \tstruct lws_context\t*context;\n@@ -83,6 +85,7 @@ typedef struct {\n \n \tuint8_t\t\t\trecursion;\n \tuint8_t\t\t\ttids;\n+\tuint8_t\t\t\tgo_nogo;\n \n \tuint8_t\t\t\tis_retry:1;\n \ndiff --git a/lib/system/metrics/CMakeLists.txt b/lib/system/metrics/CMakeLists.txt\nnew file mode 100644\nindex 0000000..3ed7f3f\n--- /dev/null\n+++ b/lib/system/metrics/CMakeLists.txt\n@@ -0,0 +1,10 @@\n+include_directories(.)\n+\n+if (LWS_WITH_SYS_METRICS)\n+\tlist(APPEND SOURCES\n+\t\tsystem/metrics/metrics.c\n+\t)\n+endif()\n+\n+exports_to_parent_scope()\n+\ndiff --git a/lib/system/metrics/metrics.c b/lib/system/metrics/metrics.c\nnew file mode 100644\nindex 0000000..95a599f\n--- /dev/null\n+++ b/lib/system/metrics/metrics.c\n@@ -0,0 +1,891 @@\n+/*\n+ * lws Generic Metrics\n+ *\n+ * Copyright (C) 2019 - 2021 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+#include \u0022private-lib-core.h\u0022\n+#include \u003cassert.h\u003e\n+\n+int\n+lws_metrics_tag_add(lws_dll2_owner_t *owner, const char *name, const char *val)\n+{\n+\tsize_t vl \u003d strlen(val);\n+\tlws_metrics_tag_t *tag;\n+\n+\t// lwsl_notice(\u0022%s: adding %s\u003d%s\u005cn\u0022, __func__, name, val);\n+\n+\t/*\n+\t * Remove (in order to replace) any existing tag of same name\n+\t */\n+\n+\tlws_start_foreach_dll(struct lws_dll2 *, d, owner-\u003ehead) {\n+\t\ttag \u003d lws_container_of(d, lws_metrics_tag_t, list);\n+\n+\t\tif (!strcmp(name, tag-\u003ename)) {\n+\t\t\tlws_dll2_remove(\u0026tag-\u003elist);\n+\t\t\tlws_free(tag);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t} lws_end_foreach_dll(d);\n+\n+\t/*\n+\t * Create the new tag\n+\t */\n+\n+\ttag \u003d lws_malloc(sizeof(*tag) + vl + 1, __func__);\n+\tif (!tag)\n+\t\treturn 1;\n+\n+\tlws_dll2_clear(\u0026tag-\u003elist);\n+\ttag-\u003ename \u003d name;\n+\tmemcpy(\u0026tag[1], val, vl + 1);\n+\n+\tlws_dll2_add_tail(\u0026tag-\u003elist, owner);\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_metrics_tag_wsi_add(struct lws *wsi, const char *name, const char *val)\n+{\n+\t__lws_lc_tag(NULL, \u0026wsi-\u003elc, \u0022|%s\u0022, val);\n+\n+\treturn lws_metrics_tag_add(\u0026wsi-\u003ecal_conn.mtags_owner, name, val);\n+}\n+\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+int\n+lws_metrics_tag_ss_add(struct lws_ss_handle *ss, const char *name, const char *val)\n+{\n+\t__lws_lc_tag(NULL, \u0026ss-\u003elc, \u0022|%s\u0022, val);\n+\treturn lws_metrics_tag_add(\u0026ss-\u003ecal_txn.mtags_owner, name, val);\n+}\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+int\n+lws_metrics_tag_sspc_add(struct lws_sspc_handle *sspc, const char *name,\n+\t\t\t const char *val)\n+{\n+\t__lws_lc_tag(NULL, \u0026sspc-\u003elc, \u0022|%s\u0022, val);\n+\treturn lws_metrics_tag_add(\u0026sspc-\u003ecal_txn.mtags_owner, name, val);\n+}\n+#endif\n+#endif\n+\n+void\n+lws_metrics_tags_destroy(lws_dll2_owner_t *owner)\n+{\n+\tlws_metrics_tag_t *t;\n+\n+\tlws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, owner-\u003ehead) {\n+\t\tt \u003d lws_container_of(d, lws_metrics_tag_t, list);\n+\n+\t\tlws_dll2_remove(\u0026t-\u003elist);\n+\t\tlws_free(t);\n+\n+\t} lws_end_foreach_dll_safe(d, d1);\n+}\n+\n+size_t\n+lws_metrics_tags_serialize(lws_dll2_owner_t *owner, char *buf, size_t len)\n+{\n+\tchar *end \u003d buf + len - 1, *p \u003d buf;\n+\tlws_metrics_tag_t *t;\n+\n+\tlws_start_foreach_dll(struct lws_dll2 *, d, owner-\u003ehead) {\n+\t\tt \u003d lws_container_of(d, lws_metrics_tag_t, list);\n+\n+\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p),\n+\t\t\t\t \u0022%s\u003d\u005c\u0022%s\u005c\u0022\u0022, t-\u003ename, (const char *)\u0026t[1]);\n+\n+\t\tif (d-\u003enext \u0026\u0026 p + 2 \u003c end)\n+\t\t\t*p++ \u003d ',';\n+\n+\t} lws_end_foreach_dll(d);\n+\n+\t*p \u003d '\u005c0';\n+\n+\treturn lws_ptr_diff_size_t(p, buf);\n+}\n+\n+const char *\n+lws_metrics_tag_get(lws_dll2_owner_t *owner, const char *name)\n+{\n+\tlws_metrics_tag_t *t;\n+\n+\tlws_start_foreach_dll(struct lws_dll2 *, d, owner-\u003ehead) {\n+\t\tt \u003d lws_container_of(d, lws_metrics_tag_t, list);\n+\n+\t\tif (!strcmp(name, t-\u003ename))\n+\t\t\treturn (const char *)\u0026t[1];\n+\n+\t} lws_end_foreach_dll(d);\n+\n+\treturn NULL;\n+}\n+\n+static int\n+lws_metrics_dump_cb(lws_metric_pub_t *pub, void *user);\n+\n+static void\n+lws_metrics_report_and_maybe_clear(struct lws_context *ctx, lws_metric_pub_t *pub)\n+{\n+\tif (!pub-\u003eus_first || pub-\u003eus_last \u003d\u003d pub-\u003eus_dumped)\n+\t\treturn;\n+\n+\tlws_metrics_dump_cb(pub, ctx);\n+}\n+\n+static void\n+lws_metrics_periodic_cb(lws_sorted_usec_list_t *sul)\n+{\n+\tlws_metric_policy_dyn_t *dmp \u003d lws_container_of(sul,\n+\t\t\t\t\t\tlws_metric_policy_dyn_t, sul);\n+\tstruct lws_context *ctx \u003d lws_container_of(dmp-\u003elist.owner,\n+\t\t\t\t\tstruct lws_context, owner_mtr_dynpol);\n+\n+\tif (!ctx-\u003esystem_ops || !ctx-\u003esystem_ops-\u003emetric_report)\n+\t\treturn;\n+\n+\tlws_start_foreach_dll(struct lws_dll2 *, d, dmp-\u003eowner.head) {\n+\t\tlws_metric_t *mt \u003d lws_container_of(d, lws_metric_t, list);\n+\t\tlws_metric_pub_t *pub \u003d lws_metrics_priv_to_pub(mt);\n+\n+\t\tlws_metrics_report_and_maybe_clear(ctx, pub);\n+\n+\t} lws_end_foreach_dll(d);\n+\n+#if defined(LWS_WITH_SYS_SMD) \u0026\u0026 defined(LWS_WITH_SECURE_STREAMS)\n+\t(void)lws_smd_msg_printf(ctx, LWSSMDCL_METRICS,\n+\t\t\t\t \u0022{\u005c\u0022dump\u005c\u0022:\u005c\u0022%s\u005c\u0022,\u005c\u0022ts\u005c\u0022:%lu}\u0022,\n+\t\t\t\t dmp-\u003epolicy-\u003ename,\n+\t\t\t\t (long)ctx-\u003elast_policy);\n+#endif\n+\n+\tif (dmp-\u003epolicy-\u003eus_schedule)\n+\t\tlws_sul_schedule(ctx, 0, \u0026dmp-\u003esul,\n+\t\t\t\t lws_metrics_periodic_cb,\n+\t\t\t\t dmp-\u003epolicy-\u003eus_schedule);\n+}\n+\n+/*\n+ * Policies are in two pieces, a const policy and a dynamic part that contains\n+ * lists and sul timers for the policy etc. This creates a dynmic part\n+ * corresponding to the static part.\n+ *\n+ * Metrics can exist detached from being bound to any policy about how to\n+ * report them, these are collected but not reported unless they later become\n+ * bound to a reporting policy dynamically.\n+ */\n+\n+lws_metric_policy_dyn_t *\n+lws_metrics_policy_dyn_create(struct lws_context *ctx,\n+\t\t\t const lws_metric_policy_t *po)\n+{\n+\tlws_metric_policy_dyn_t *dmet;\n+\n+\tdmet \u003d lws_zalloc(sizeof(*dmet), __func__);\n+\tif (!dmet)\n+\t\treturn NULL;\n+\n+\tdmet-\u003epolicy \u003d po;\n+\tlws_dll2_add_tail(\u0026dmet-\u003elist, \u0026ctx-\u003eowner_mtr_dynpol);\n+\n+\tif (po-\u003eus_schedule)\n+\t\tlws_sul_schedule(ctx, 0, \u0026dmet-\u003esul,\n+\t\t\t\t lws_metrics_periodic_cb,\n+\t\t\t\t po-\u003eus_schedule);\n+\n+\treturn dmet;\n+}\n+\n+/*\n+ * Get a dynamic metrics policy from the const one, may return NULL if OOM\n+ */\n+\n+lws_metric_policy_dyn_t *\n+lws_metrics_policy_get_dyn(struct lws_context *ctx,\n+\t\t\t const lws_metric_policy_t *po)\n+{\n+\tlws_start_foreach_dll(struct lws_dll2 *, d, ctx-\u003eowner_mtr_dynpol.head) {\n+\t\tlws_metric_policy_dyn_t *dm \u003d\n+\t\t\tlws_container_of(d, lws_metric_policy_dyn_t, list);\n+\n+\t\tif (dm-\u003epolicy \u003d\u003d po)\n+\t\t\treturn dm;\n+\n+\t} lws_end_foreach_dll(d);\n+\n+\t/*\n+\t * no dyn policy part for this const policy --\u003e create one\n+\t *\n+\t * We want a dynamic part for listing metrics that bound to the policy\n+\t */\n+\n+\treturn lws_metrics_policy_dyn_create(ctx, po);\n+}\n+\n+static int\n+lws_metrics_check_in_policy(const char *polstring, const char *name)\n+{\n+\tstruct lws_tokenize ts;\n+\n+\tmemset(\u0026ts, 0, sizeof(ts));\n+\n+\tts.start \u003d polstring;\n+\tts.len \u003d strlen(polstring);\n+\tts.flags \u003d (uint16_t)(LWS_TOKENIZE_F_MINUS_NONTERM |\n+\t\t\t LWS_TOKENIZE_F_ASTERISK_NONTERM |\n+\t\t\t LWS_TOKENIZE_F_COMMA_SEP_LIST |\n+\t\t\t LWS_TOKENIZE_F_NO_FLOATS |\n+\t\t\t LWS_TOKENIZE_F_DOT_NONTERM);\n+\n+\tdo {\n+\t\tts.e \u003d (int8_t)lws_tokenize(\u0026ts);\n+\n+\t\tif (ts.e \u003d\u003d LWS_TOKZE_TOKEN) {\n+\t\t\tif (!lws_strcmp_wildcard(ts.token, ts.token_len, name))\n+\t\t\t\t/* yes, we are mentioned in this guy's policy */\n+\t\t\t\treturn 0;\n+\t\t}\n+\t} while (ts.e \u003e 0);\n+\n+\t/* no, this policy doesn't apply to a metric with our name */\n+\n+\treturn 1;\n+}\n+\n+static const lws_metric_policy_t *\n+lws_metrics_find_policy(struct lws_context *ctx, const char *name)\n+{\n+\tconst lws_metric_policy_t *mp \u003d ctx-\u003emetrics_policies;\n+\n+\tif (!mp) {\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\t\tif (ctx-\u003epss_policies)\n+\t\t\tmp \u003d ctx-\u003epss_policies-\u003emetrics;\n+#endif\n+\t\tif (!mp)\n+\t\t\treturn NULL;\n+\t}\n+\n+\twhile (mp) {\n+\t\tif (mp-\u003ereport \u0026\u0026 !lws_metrics_check_in_policy(mp-\u003ereport, name))\n+\t\t\treturn mp;\n+\n+\t\tmp \u003d mp-\u003enext;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+/*\n+ * Create a lws_metric_t, bind to a named policy if possible (or add to the\n+ * context list of unbound metrics) and set its lws_system\n+ * idx. The metrics objects themselves are typically composed into other\n+ * objects and are well-known composed members of them.\n+ */\n+\n+lws_metric_t *\n+lws_metric_create(struct lws_context *ctx, uint8_t flags, const char *name)\n+{\n+\tconst lws_metric_policy_t *po;\n+\tlws_metric_policy_dyn_t *dmp;\n+\tlws_metric_pub_t *pub;\n+\tlws_metric_t *mt;\n+\tchar pname[32];\n+\tsize_t nl;\n+\n+\tif (ctx-\u003emetrics_prefix) {\n+\n+\t\t/*\n+\t\t * In multi-process case, we want to prefix metrics from this\n+\t\t * process / context with a string distinguishing which\n+\t\t * application they came from\n+\t\t */\n+\n+\t\tnl \u003d (size_t)lws_snprintf(pname, sizeof(pname) - 1, \u0022%s.%s\u0022,\n+\t\t\t\t ctx-\u003emetrics_prefix, name);\n+\t\tname \u003d pname;\n+\t} else\n+\t\tnl \u003d strlen(name);\n+\n+\tmt \u003d (lws_metric_t *)lws_zalloc(sizeof(*mt) /* private */ +\n+\t\t\t\t\tsizeof(lws_metric_pub_t) +\n+\t\t\t\t\tnl + 1 /* copy of metric name */,\n+\t\t\t\t\t__func__);\n+\tif (!mt)\n+\t\treturn NULL;\n+\n+\tpub \u003d lws_metrics_priv_to_pub(mt);\n+\tpub-\u003ename \u003d (char *)pub + sizeof(lws_metric_pub_t);\n+\tmemcpy((char *)pub-\u003ename, name, nl + 1);\n+\tpub-\u003eflags \u003d flags;\n+\n+\t/* after these common members, we have to use the right type */\n+\n+\tif (!(flags \u0026 LWSMTFL_REPORT_HIST)) {\n+\t\t/* anything is smaller or equal to this */\n+\t\tpub-\u003eu.agg.min \u003d ~(u_mt_t)0;\n+\t\tpub-\u003eus_first \u003d lws_now_usecs();\n+\t}\n+\n+\tmt-\u003ectx \u003d ctx;\n+\n+\t/*\n+\t * Let's see if we can bind to a reporting policy straight away\n+\t */\n+\n+\tpo \u003d lws_metrics_find_policy(ctx, name);\n+\tif (po) {\n+\t\tdmp \u003d lws_metrics_policy_get_dyn(ctx, po);\n+\t\tif (dmp) {\n+\t\t\tlwsl_notice(\u0022%s: metpol %s\u005cn\u0022, __func__, name);\n+\t\t\tlws_dll2_add_tail(\u0026mt-\u003elist, \u0026dmp-\u003eowner);\n+\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * If not, well, let's go on without and maybe later at runtime, he'll\n+\t * get interested in us and apply a reporting policy\n+\t */\n+\n+\tlws_dll2_add_tail(\u0026mt-\u003elist, \u0026ctx-\u003eowner_mtr_no_pol);\n+\n+\treturn mt;\n+}\n+\n+/*\n+ * If our metric is bound to a reporting policy, return a pointer to it,\n+ * otherwise NULL\n+ */\n+\n+const lws_metric_policy_t *\n+lws_metric_get_policy(lws_metric_t *mt)\n+{\n+\tlws_metric_policy_dyn_t *dp;\n+\n+\t/*\n+\t * Our metric must either be on the \u0022no policy\u0022 context list or\n+\t * listed by the dynamic part of the policy it is bound to\n+\t */\n+\tassert(mt-\u003elist.owner);\n+\n+\tif ((char *)mt-\u003elist.owner \u003e\u003d (char *)mt-\u003ectx \u0026\u0026\n+\t (char *)mt-\u003elist.owner \u003c (char *)mt-\u003ectx + sizeof(struct lws_context))\n+\t\t/* we are on the \u0022no policy\u0022 context list */\n+\t\treturn NULL;\n+\n+\t/* we are listed by a dynamic policy owner */\n+\n+\tdp \u003d lws_container_of(mt-\u003elist.owner, lws_metric_policy_dyn_t, owner);\n+\n+\t/* return the const policy the dynamic policy represents */\n+\n+\treturn dp-\u003epolicy;\n+}\n+\n+void\n+lws_metric_rebind_policies(struct lws_context *ctx)\n+{\n+\tconst lws_metric_policy_t *po;\n+\tlws_metric_policy_dyn_t *dmp;\n+\n+\tlws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,\n+\t\t\t\t ctx-\u003eowner_mtr_no_pol.head) {\n+\t\tlws_metric_t *mt \u003d lws_container_of(d, lws_metric_t, list);\n+\t\tlws_metric_pub_t *pub \u003d lws_metrics_priv_to_pub(mt);\n+\n+\t\tpo \u003d lws_metrics_find_policy(ctx, pub-\u003ename);\n+\t\tif (po) {\n+\t\t\tdmp \u003d lws_metrics_policy_get_dyn(ctx, po);\n+\t\t\tif (dmp) {\n+\t\t\t\tlwsl_info(\u0022%s: %s \u003c- pol %s\u005cn\u0022, __func__,\n+\t\t\t\t\t\tpub-\u003ename, po-\u003ename);\n+\t\t\t\tlws_dll2_remove(\u0026mt-\u003elist);\n+\t\t\t\tlws_dll2_add_tail(\u0026mt-\u003elist, \u0026dmp-\u003eowner);\n+\t\t\t}\n+\t\t} else\n+\t\t\tlwsl_debug(\u0022%s: no pol for %s\u005cn\u0022, __func__, pub-\u003ename);\n+\n+\t} lws_end_foreach_dll_safe(d, d1);\n+}\n+\n+int\n+lws_metric_destroy(lws_metric_t **pmt, int keep)\n+{\n+\tlws_metric_t *mt \u003d *pmt;\n+\tlws_metric_pub_t *pub \u003d lws_metrics_priv_to_pub(mt);\n+\n+\tif (!mt)\n+\t\treturn 0;\n+\n+\tlws_dll2_remove(\u0026mt-\u003elist);\n+\n+\tif (keep) {\n+\t\tlws_dll2_add_tail(\u0026mt-\u003elist, \u0026mt-\u003ectx-\u003eowner_mtr_no_pol);\n+\n+\t\treturn 0;\n+\t}\n+\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_HIST) {\n+\t\tlws_metric_bucket_t *b \u003d pub-\u003eu.hist.head, *b1;\n+\n+\t\tpub-\u003eu.hist.head \u003d NULL;\n+\n+\t\twhile (b) {\n+\t\t\tb1 \u003d b-\u003enext;\n+\t\t\tlws_free(b);\n+\t\t\tb \u003d b1;\n+\t\t}\n+\t}\n+\n+\tlws_free(mt);\n+\t*pmt \u003d NULL;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Allow an existing metric to have its reporting policy changed at runtime\n+ */\n+\n+int\n+lws_metric_switch_policy(lws_metric_t *mt, const char *polname)\n+{\n+\tconst lws_metric_policy_t *po;\n+\tlws_metric_policy_dyn_t *dmp;\n+\n+\tpo \u003d lws_metrics_find_policy(mt-\u003ectx, polname);\n+\tif (!po)\n+\t\treturn 1;\n+\n+\tdmp \u003d lws_metrics_policy_get_dyn(mt-\u003ectx, po);\n+\tif (!dmp)\n+\t\treturn 1;\n+\n+\tlws_dll2_remove(\u0026mt-\u003elist);\n+\tlws_dll2_add_tail(\u0026mt-\u003elist, \u0026dmp-\u003eowner);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * If keep is set, don't destroy existing metrics objects, just detach them\n+ * from the policy being deleted and keep track of them on ctx-\u003e\n+ * owner_mtr_no_pol\n+ */\n+\n+void\n+lws_metric_policy_dyn_destroy(lws_metric_policy_dyn_t *dm, int keep)\n+{\n+\tlws_sul_cancel(\u0026dm-\u003esul);\n+\n+\tlws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, dm-\u003eowner.head) {\n+\t\tlws_metric_t *m \u003d lws_container_of(d, lws_metric_t, list);\n+\n+\t\tlws_metric_destroy(\u0026m, keep);\n+\n+\t} lws_end_foreach_dll_safe(d, d1);\n+\n+\tlws_sul_cancel(\u0026dm-\u003esul);\n+\n+\tlws_dll2_remove(\u0026dm-\u003elist);\n+\tlws_free(dm);\n+}\n+\n+/*\n+ * Destroy all dynamic metrics policies, deinit any metrics still using them\n+ */\n+\n+void\n+lws_metrics_destroy(struct lws_context *ctx)\n+{\n+\tlws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,\n+\t\t\t\t ctx-\u003eowner_mtr_dynpol.head) {\n+\t\tlws_metric_policy_dyn_t *dm \u003d\n+\t\t\tlws_container_of(d, lws_metric_policy_dyn_t, list);\n+\n+\t\tlws_metric_policy_dyn_destroy(dm, 0); /* don't keep */\n+\n+\t} lws_end_foreach_dll_safe(d, d1);\n+\n+\t/* destroy metrics with no current policy too... */\n+\n+\tlws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,\n+\t\t\t\t ctx-\u003eowner_mtr_no_pol.head) {\n+\t\tlws_metric_t *mt \u003d lws_container_of(d, lws_metric_t, list);\n+\n+\t\tlws_metric_destroy(\u0026mt, 0); /* don't keep */\n+\n+\t} lws_end_foreach_dll_safe(d, d1);\n+\n+\t/* ... that's the whole allocated metrics footprint gone... */\n+}\n+\n+int\n+lws_metrics_hist_bump_(lws_metric_pub_t *pub, const char *name)\n+{\n+\tlws_metric_bucket_t *buck \u003d pub-\u003eu.hist.head;\n+\tsize_t nl \u003d strlen(name);\n+\tchar *nm;\n+\n+\tif (!(pub-\u003eflags \u0026 LWSMTFL_REPORT_HIST)) {\n+\t\tlwsl_err(\u0022%s: %s not histogram: flags %d\u005cn\u0022, __func__,\n+\t\t\t\tpub-\u003ename, pub-\u003eflags);\n+\t\tassert(0);\n+\t}\n+\tassert(nl \u003c 255);\n+\n+\tpub-\u003eus_last \u003d lws_now_usecs();\n+\tif (!pub-\u003eus_first)\n+\t\tpub-\u003eus_first \u003d pub-\u003eus_last;\n+\n+\twhile (buck) {\n+\t\tif (lws_metric_bucket_name_len(buck) \u003d\u003d nl \u0026\u0026\n+\t\t !strcmp(name, lws_metric_bucket_name(buck))) {\n+\t\t\tbuck-\u003ecount++;\n+\t\t\tgoto happy;\n+\t\t}\n+\t\tbuck \u003d buck-\u003enext;\n+\t}\n+\n+\tbuck \u003d lws_malloc(sizeof(*buck) + nl + 2, __func__);\n+\tif (!buck)\n+\t\treturn 1;\n+\n+\tnm \u003d (char *)buck + sizeof(*buck);\n+\t/* length byte at beginning of name, avoid struct alignment overhead */\n+\t*nm \u003d (char)nl;\n+\tmemcpy(nm + 1, name, nl + 1);\n+\n+\tbuck-\u003enext \u003d pub-\u003eu.hist.head;\n+\tpub-\u003eu.hist.head \u003d buck;\n+\tbuck-\u003ecount \u003d 1;\n+\tpub-\u003eu.hist.list_size++;\n+\n+happy:\n+\tpub-\u003eu.hist.total_count++;\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_metrics_hist_bump_describe_wsi(struct lws *wsi, lws_metric_pub_t *pub,\n+\t\t\t\t const char *name)\n+{\n+\tchar desc[192], d1[48], *p \u003d desc, *end \u003d desc + sizeof(desc);\n+\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\tif (wsi-\u003eclient_bound_sspc) {\n+\t\tlws_sspc_handle_t *h \u003d (lws_sspc_handle_t *)wsi-\u003ea.opaque_user_data;\n+\t\tif (h)\n+\t\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022ss\u003d\u005c\u0022%s\u005c\u0022,\u0022,\n+\t\t\t\t h-\u003essi.streamtype);\n+\t} else\n+\t\tif (wsi-\u003eclient_proxy_onward) {\n+\t\t\tstruct conn *conn \u003d (struct conn *)wsi-\u003ea.opaque_user_data;\n+\n+\t\t\tif (conn \u0026\u0026 conn-\u003ess)\n+\t\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022ss\u003d\u005c\u0022%s\u005c\u0022,\u0022,\n+\t\t\t\t conn-\u003ess-\u003einfo.streamtype);\n+\t\t} else\n+#endif\n+\tif (wsi-\u003efor_ss) {\n+\t\tlws_ss_handle_t *h \u003d (lws_ss_handle_t *)wsi-\u003ea.opaque_user_data;\n+\t\tif (h)\n+\t\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022ss\u003d\u005c\u0022%s\u005c\u0022,\u0022,\n+\t\t\t\t h-\u003einfo.streamtype);\n+\t}\n+#endif\n+\n+#if defined(LWS_WITH_CLIENT)\n+\tif (wsi-\u003estash \u0026\u0026 wsi-\u003estash-\u003ecis[CIS_HOST])\n+\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022hostname\u003d\u005c\u0022%s\u005c\u0022,\u0022,\n+\t\t\t\twsi-\u003estash-\u003ecis[CIS_HOST]);\n+#endif\n+\n+\tlws_sa46_write_numeric_address(\u0026wsi-\u003esa46_peer, d1, sizeof(d1));\n+\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022peer\u003d\u005c\u0022%s\u005c\u0022,\u0022, d1);\n+\n+\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022%s\u0022, name);\n+\n+\tlws_metrics_hist_bump_(pub, desc);\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_metrics_foreach(struct lws_context *ctx, void *user,\n+\t\t int (*cb)(lws_metric_pub_t *pub, void *user))\n+{\n+\tint n;\n+\n+\tlws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,\n+\t\t\t\t ctx-\u003eowner_mtr_no_pol.head) {\n+\t\tlws_metric_t *mt \u003d lws_container_of(d, lws_metric_t, list);\n+\n+\t\tn \u003d cb(lws_metrics_priv_to_pub(mt), user);\n+\t\tif (n)\n+\t\t\treturn n;\n+\n+\t} lws_end_foreach_dll_safe(d, d1);\n+\n+\tlws_start_foreach_dll_safe(struct lws_dll2 *, d2, d3,\n+\t\t\t\t ctx-\u003eowner_mtr_dynpol.head) {\n+\t\tlws_metric_policy_dyn_t *dm \u003d\n+\t\t\tlws_container_of(d2, lws_metric_policy_dyn_t, list);\n+\n+\t\tlws_start_foreach_dll_safe(struct lws_dll2 *, e, e1,\n+\t\t\t\t\t dm-\u003eowner.head) {\n+\n+\t\t\tlws_metric_t *mt \u003d lws_container_of(e, lws_metric_t, list);\n+\n+\t\t\tn \u003d cb(lws_metrics_priv_to_pub(mt), user);\n+\t\t\tif (n)\n+\t\t\t\treturn n;\n+\n+\t\t} lws_end_foreach_dll_safe(e, e1);\n+\n+\t} lws_end_foreach_dll_safe(d2, d3);\n+\n+\treturn 0;\n+}\n+\n+static int\n+lws_metrics_dump_cb(lws_metric_pub_t *pub, void *user)\n+{\n+\tstruct lws_context *ctx \u003d (struct lws_context *)user;\n+\tint n;\n+\n+\tif (!ctx-\u003esystem_ops || !ctx-\u003esystem_ops-\u003emetric_report)\n+\t\treturn 0;\n+\n+\t/*\n+\t * return nonzero to reset stats\n+\t */\n+\n+\tn \u003d ctx-\u003esystem_ops-\u003emetric_report(pub);\n+\n+\t/* track when we dumped it... */\n+\n+\tpub-\u003eus_first \u003d pub-\u003eus_dumped \u003d lws_now_usecs();\n+\tpub-\u003eus_last \u003d 0;\n+\n+\tif (!n)\n+\t\treturn 0;\n+\n+\t/* ... and clear it back to 0 */\n+\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_HIST) {\n+\t\tlws_metric_bucket_t *b \u003d pub-\u003eu.hist.head, *b1;\n+\t\tpub-\u003eu.hist.head \u003d NULL;\n+\n+\t\twhile (b) {\n+\t\t\tb1 \u003d b-\u003enext;\n+\t\t\tlws_free(b);\n+\t\t\tb \u003d b1;\n+\t\t}\n+\t\tpub-\u003eu.hist.total_count \u003d 0;\n+\t\tpub-\u003eu.hist.list_size \u003d 0;\n+\t} else\n+\t\tmemset(\u0026pub-\u003eu.agg, 0, sizeof(pub-\u003eu.agg));\n+\n+\treturn 0;\n+}\n+\n+void\n+lws_metrics_dump(struct lws_context *ctx)\n+{\n+\tlws_metrics_foreach(ctx, ctx, lws_metrics_dump_cb);\n+}\n+\n+static int\n+_lws_metrics_format(lws_metric_pub_t *pub, lws_usec_t now, int gng,\n+\t\t char *buf, size_t len)\n+{\n+\tconst lws_humanize_unit_t *schema \u003d humanize_schema_si;\n+\tchar *end \u003d buf + len - 1, *obuf \u003d buf;\n+\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_DUTY_WALLCLOCK_US)\n+\t\tschema \u003d humanize_schema_us;\n+\n+\tif (!(pub-\u003eflags \u0026 LWSMTFL_REPORT_MEAN)) {\n+\t\t/* only the sum is meaningful */\n+\t\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_DUTY_WALLCLOCK_US) {\n+\n+\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022 %u, \u0022,\n+\t\t\t\t\t\t(unsigned int)pub-\u003eu.agg.count[gng]);\n+\n+\t\t\tbuf +\u003d lws_humanize(buf, lws_ptr_diff_size_t(end, buf),\n+\t\t\t\t\t (uint64_t)pub-\u003eu.agg.sum[gng],\n+\t\t\t\t\t humanize_schema_us);\n+\n+\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022 / \u0022);\n+\n+\t\t\tbuf +\u003d lws_humanize(buf, lws_ptr_diff_size_t(end, buf),\n+\t\t\t\t\t (uint64_t)(now - pub-\u003eus_first),\n+\t\t\t\t\t humanize_schema_us);\n+\n+\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n+\t\t\t\t\t \u0022 (%d%%)\u0022, (int)((100 * pub-\u003eu.agg.sum[gng]) /\n+\t\t\t\t\t\t(unsigned long)(now - pub-\u003eus_first)));\n+\t\t} else {\n+\t\t\t/* it's a monotonic ordinal, like total tx */\n+\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022(%u) \u0022,\n+\t\t\t\t\t(unsigned int)pub-\u003eu.agg.count[gng]);\n+\t\t\tbuf +\u003d lws_humanize(buf, lws_ptr_diff_size_t(end, buf),\n+\t\t\t\t\t (uint64_t)pub-\u003eu.agg.sum[gng],\n+\t\t\t\t\t humanize_schema_si);\n+\t\t}\n+\n+\t} else {\n+\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022%u, mean: \u0022, (unsigned int)pub-\u003eu.agg.count[gng]);\n+\t\t/* the average over the period is meaningful */\n+\t\tbuf +\u003d lws_humanize(buf, lws_ptr_diff_size_t(end, buf),\n+\t\t\t\t (uint64_t)(pub-\u003eu.agg.count[gng] ?\n+\t\t\t\t\t pub-\u003eu.agg.sum[gng] / pub-\u003eu.agg.count[gng] : 0),\n+\t\t\t\t schema);\n+\t}\n+\n+\treturn lws_ptr_diff(buf, obuf);\n+}\n+\n+int\n+lws_metrics_format(lws_metric_pub_t *pub, lws_metric_bucket_t **sub, char *buf, size_t len)\n+{\n+\tchar *end \u003d buf + len - 1, *obuf \u003d buf;\n+\tlws_usec_t t \u003d lws_now_usecs();\n+\tconst lws_humanize_unit_t *schema \u003d humanize_schema_si;\n+\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_DUTY_WALLCLOCK_US)\n+\t\tschema \u003d humanize_schema_us;\n+\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_HIST) {\n+\n+\t\tif (*sub \u003d\u003d NULL)\n+\t\t\treturn 0;\n+\n+\t\tif (*sub) {\n+\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n+\t\t\t\t\t \u0022%s{%s} %llu\u0022, pub-\u003ename,\n+\t\t\t\t\t lws_metric_bucket_name(*sub),\n+\t\t\t\t\t (unsigned long long)(*sub)-\u003ecount);\n+\n+\t\t\t*sub \u003d (*sub)-\u003enext;\n+\t\t}\n+\n+\t\tgoto happy;\n+\t}\n+\n+\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022%s: \u0022,\n+\t\t\t\tpub-\u003ename);\n+\n+\tif (!pub-\u003eu.agg.count[METRES_GO] \u0026\u0026 !pub-\u003eu.agg.count[METRES_NOGO])\n+\t\treturn 0;\n+\n+\tif (pub-\u003eu.agg.count[METRES_GO]) {\n+\t\tif (!(pub-\u003eflags \u0026 LWSMTFL_REPORT_ONLY_GO))\n+\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n+\t\t\t\t\t \u0022Go: \u0022);\n+\t\tbuf +\u003d _lws_metrics_format(pub, t, METRES_GO, buf,\n+\t\t\t\t\t lws_ptr_diff_size_t(end, buf));\n+\t}\n+\n+\tif (!(pub-\u003eflags \u0026 LWSMTFL_REPORT_ONLY_GO) \u0026\u0026 pub-\u003eu.agg.count[METRES_NOGO]) {\n+\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022, NoGo: \u0022);\n+\t\tbuf +\u003d _lws_metrics_format(pub, t, METRES_NOGO, buf,\n+\t\t\t\t\t lws_ptr_diff_size_t(end, buf));\n+\t}\n+\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_MEAN) {\n+\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022, min: \u0022);\n+\t\tbuf +\u003d lws_humanize(buf, lws_ptr_diff_size_t(end, buf), pub-\u003eu.agg.min,\n+\t\t\t\t schema);\n+\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), \u0022, max: \u0022);\n+\t\tbuf +\u003d lws_humanize(buf, lws_ptr_diff_size_t(end, buf), pub-\u003eu.agg.max,\n+\t\t\t\t schema);\n+\t}\n+\n+happy:\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_HIST)\n+\t\treturn 1;\n+\n+\t*sub \u003d NULL;\n+\n+\treturn lws_ptr_diff(buf, obuf);\n+}\n+\n+/*\n+ * We want to, at least internally, record an event... depending on the policy,\n+ * that might cause us to call through to the lws_system apis, or just update\n+ * our local stats about it and dump at the next periodic chance (also set by\n+ * the policy)\n+ */\n+\n+void\n+lws_metric_event(lws_metric_t *mt, char go_nogo, u_mt_t val)\n+{\n+\tlws_metric_pub_t *pub;\n+\n+\tassert((go_nogo \u0026 0xfe) \u003d\u003d 0);\n+\n+\tif (!mt)\n+\t\treturn;\n+\n+\tpub \u003d lws_metrics_priv_to_pub(mt);\n+\tassert(!(pub-\u003eflags \u0026 LWSMTFL_REPORT_HIST));\n+\n+\tpub-\u003eus_last \u003d lws_now_usecs();\n+\tif (!pub-\u003eus_first)\n+\t\tpub-\u003eus_first \u003d pub-\u003eus_last;\n+\tpub-\u003eu.agg.count[(int)go_nogo]++;\n+\tpub-\u003eu.agg.sum[(int)go_nogo] +\u003d val;\n+\tif (val \u003e pub-\u003eu.agg.max)\n+\t\tpub-\u003eu.agg.max \u003d val;\n+\tif (val \u003c pub-\u003eu.agg.min)\n+\t\tpub-\u003eu.agg.min \u003d val;\n+\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_OOB)\n+\t\tlws_metrics_report_and_maybe_clear(mt-\u003ectx, pub);\n+}\n+\n+\n+void\n+lws_metrics_hist_bump_priv_tagged(lws_metric_pub_t *mt, lws_dll2_owner_t *tow,\n+\t\t\t\t lws_dll2_owner_t *tow2)\n+{\n+\tchar qual[192];\n+\tsize_t p;\n+\n+\tp \u003d lws_metrics_tags_serialize(tow, qual, sizeof(qual));\n+\tif (tow2)\n+\t\tlws_metrics_tags_serialize(tow2, qual + p,\n+\t\t\t\tsizeof(qual) - p);\n+\n+\tlwsl_warn(\u0022%s: '%s'\u005cn\u0022, __func__, qual);\n+\n+\tlws_metrics_hist_bump(mt, qual);\n+}\ndiff --git a/lib/system/metrics/private-lib-system-metrics.h b/lib/system/metrics/private-lib-system-metrics.h\nnew file mode 100644\nindex 0000000..07f4daf\n--- /dev/null\n+++ b/lib/system/metrics/private-lib-system-metrics.h\n@@ -0,0 +1,124 @@\n+/*\n+ * lws System Metrics\n+ *\n+ * Copyright (C) 2021 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+/*\n+ * Const struct that describes a policy for processing raw metrics to turn them\n+ * into events.\n+ *\n+ * Typically although we want to monitor every event, the data produced can be\n+ * too large, and many events that are \u0022normal\u0022 just need to be counted as such;\n+ * outliers or change-to-continuous outliers may deserve closer recording as\n+ * events in their own right.\n+ *\n+ * Mean computation must \u0022decay\u0022 as it ages, we do this by halving the sum and\n+ * count after .us_decay_unit us.\n+ *\n+ * We don't acknowledge outliers until there are at least .min_contributors\n+ * in the current mean (which is subject to decaying)\n+ *\n+ * We decide something is an outlier event if it deviates from the mean by\n+ * .pc_outlier_deviation %.\n+ */\n+\n+/*\n+ * The dynamic counterpart for each static metric policy, this is on heap\n+ * one per const lws_metric_policy_t. It's listed in context-\u003eowner_mtr_dynpol\n+ */\n+\n+typedef struct lws_metric_policy_dyn {\n+\tconst lws_metric_policy_t\t*policy;\n+\t/**\u003c the static part of the policy we belong to... can be NULL if no\n+\t * policy matches or the policy was invalidated */\n+\n+\tlws_dll2_owner_t\t\towner;\n+\t/**\u003c list of metrics that are using this policy */\n+\n+\tlws_dll2_t\t\t\tlist;\n+\t/**\u003c context owns us */\n+\n+\tlws_sorted_usec_list_t\t\tsul;\n+\t/**\u003c schedule periodic reports for metrics using this policy */\n+} lws_metric_policy_dyn_t;\n+\n+/*\n+ * A metrics private part, encapsulating the public part\n+ */\n+\n+typedef struct lws_metric {\n+\n+\tlws_dll2_t\t\t\tlist;\n+\t/**\u003c owned by either 1) ctx.lws_metric_policy_dyn_t.owner, or\n+\t * 2) ctx.owner_mtr_no_pol */\n+\n+\tstruct lws_context\t\t*ctx;\n+\n+\t/* public part overallocated */\n+} lws_metric_t;\n+\n+\n+#if defined(LWS_WITH_SYS_METRICS)\n+#define lws_metrics_hist_bump_priv(_mt, _name) \u005c\n+\t\tlws_metrics_hist_bump_(lws_metrics_priv_to_pub(_mt), _name)\n+#define lws_metrics_hist_bump_priv_wsi(_wsi, _hist, _name) \u005c\n+\t\tlws_metrics_hist_bump_(lws_metrics_priv_to_pub(_wsi-\u003ea.context-\u003e_hist), _name)\n+#define lws_metrics_hist_bump_priv_ss(_ss, _hist, _name) \u005c\n+\t\tlws_metrics_hist_bump_(lws_metrics_priv_to_pub(_ss-\u003econtext-\u003e_hist), _name)\n+#define lws_metrics_priv_to_pub(_x) ((lws_metric_pub_t *)\u0026(_x)[1])\n+#else\n+#define lws_metrics_hist_bump_priv(_mt, _name)\n+#define lws_metrics_hist_bump_priv_wsi(_wsi, _hist, _name)\n+#define lws_metrics_hist_bump_priv_ss(_ss, _hist, _name)\n+#define lws_metrics_priv_to_pub(_x) ((lws_metric_pub_t *)NULL)\n+#endif\n+\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+/*\n+ * sspc-specific version that also appends the tag value to the lifecycle tag\n+ * used for logging the sspc identity\n+ */\n+int\n+lws_metrics_tag_sspc_add(struct lws_sspc_handle *ss, const char *name, const char *val);\n+#endif\n+\n+int\n+lws_metrics_register_policy(struct lws_context *ctx,\n+\t\t\t const lws_metric_policy_t *head);\n+\n+void\n+lws_metrics_destroy(struct lws_context *ctx);\n+\n+void\n+lws_metric_event(lws_metric_t *mt, char go_nogo, u_mt_t val);\n+\n+lws_metric_t *\n+lws_metric_create(struct lws_context *ctx, uint8_t flags, const char *name);\n+\n+int\n+lws_metric_destroy(lws_metric_t **mt, int keep);\n+\n+void\n+lws_metric_policy_dyn_destroy(lws_metric_policy_dyn_t *dm, int keep);\n+\n+void\n+lws_metric_rebind_policies(struct lws_context *ctx);\ndiff --git a/lib/system/smd/smd.c b/lib/system/smd/smd.c\nindex 6ba05f9..7630701 100644\n--- a/lib/system/smd/smd.c\n+++ b/lib/system/smd/smd.c\n@@ -377,6 +377,10 @@ _lws_smd_ss_rx_forward(struct lws_context *ctx, const char *tag,\n \n \t_class \u003d (lws_smd_class_t)lws_ser_ru64be(buf);\n \n+\tif (_class \u003d\u003d LWSSMDCL_METRICS) {\n+\n+\t}\n+\n \t/* only locally forward messages that we care about in this process */\n \n \tif (!(ctx-\u003esmd._class_filter \u0026 _class))\ndiff --git a/lib/tls/mbedtls/mbedtls-client.c b/lib/tls/mbedtls/mbedtls-client.c\nindex 38f8a33..cd9a585 100644\n--- a/lib/tls/mbedtls/mbedtls-client.c\n+++ b/lib/tls/mbedtls/mbedtls-client.c\n@@ -223,47 +223,75 @@ int\n lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, size_t ebuf_len)\n {\n \tint n;\n+\tunsigned int avoid \u003d 0;\n \tX509 *peer \u003d SSL_get_peer_certificate(wsi-\u003etls.ssl);\n \tstruct lws_context_per_thread *pt \u003d \u0026wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi];\n+\tconst char *type \u003d \u0022\u0022;\n \tchar *sb \u003d (char *)\u0026pt-\u003eserv_buf[0];\n \n \tif (!peer) {\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tlws_metrics_hist_bump_describe_wsi(wsi, lws_metrics_priv_to_pub(\n+\t\t\t\t\twsi-\u003ea.context-\u003emth_conn_failures),\n+\t\t\t\t\t\t \u0022tls\u003d\u005c\u0022nocert\u005c\u0022\u0022);\n+#endif\n \t\tlwsl_info(\u0022peer did not provide cert\u005cn\u0022);\n \t\tlws_snprintf(ebuf, ebuf_len, \u0022no peer cert\u0022);\n \n \t\treturn -1;\n \t}\n-\tlwsl_info(\u0022peer provided cert\u005cn\u0022);\n \n \tn \u003d (int)SSL_get_verify_result(wsi-\u003etls.ssl);\n- lwsl_debug(\u0022get_verify says %d\u005cn\u0022, n);\n+\tlwsl_debug(\u0022get_verify says %d\u005cn\u0022, n);\n \n-\tif (n \u003d\u003d X509_V_OK)\n+\tswitch (n) {\n+\tcase X509_V_OK:\n \t\treturn 0;\n \n-\tif (n \u003d\u003d X509_V_ERR_HOSTNAME_MISMATCH \u0026\u0026\n-\t (wsi-\u003etls.use_ssl \u0026 LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) {\n-\t\tlwsl_info(\u0022accepting certificate for invalid hostname\u005cn\u0022);\n-\t\treturn 0;\n+\tcase X509_V_ERR_HOSTNAME_MISMATCH:\n+\t\ttype \u003d \u0022hostname\u0022;\n+\t\tavoid \u003d LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;\n+\t\tbreak;\n+\n+\tcase X509_V_ERR_INVALID_CA:\n+\t\ttype \u003d \u0022invalidca\u0022;\n+\t\tavoid \u003d LCCSCF_ALLOW_SELFSIGNED;\n+\t\tbreak;\n+\n+\tcase X509_V_ERR_CERT_NOT_YET_VALID:\n+\t\ttype \u003d \u0022notyetvalid\u0022;\n+\t\tavoid \u003d LCCSCF_ALLOW_EXPIRED;\n+\t\tbreak;\n+\n+\tcase X509_V_ERR_CERT_HAS_EXPIRED:\n+\t\ttype \u003d \u0022expired\u0022;\n+\t\tavoid \u003d LCCSCF_ALLOW_EXPIRED;\n+\t\tbreak;\n \t}\n \n-\tif (n \u003d\u003d X509_V_ERR_INVALID_CA \u0026\u0026\n-\t (wsi-\u003etls.use_ssl \u0026 LCCSCF_ALLOW_SELFSIGNED)) {\n-\t\tlwsl_info(\u0022accepting certificate from untrusted CA\u005cn\u0022);\n-\t\treturn 0;\n+\tlwsl_info(\u0022%s: cert problem: %s\u005cn\u0022, __func__, type);\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t{\n+\t\tchar buckname[64];\n+\t\tlws_snprintf(buckname, sizeof(buckname), \u0022tls\u003d\u005c\u0022%s\u005c\u0022\u0022, type);\n+\t\tlws_metrics_hist_bump_describe_wsi(wsi,\n+\t\t lws_metrics_priv_to_pub(wsi-\u003ea.context-\u003emth_conn_failures),\n+\t\t\t buckname);\n \t}\n-\n-\tif ((n \u003d\u003d X509_V_ERR_CERT_NOT_YET_VALID ||\n-\t n \u003d\u003d X509_V_ERR_CERT_HAS_EXPIRED) \u0026\u0026\n-\t (wsi-\u003etls.use_ssl \u0026 LCCSCF_ALLOW_EXPIRED)) {\n-\t\tlwsl_info(\u0022accepting expired or not yet valid certificate\u005cn\u0022);\n+#endif\n+\tif (wsi-\u003etls.use_ssl \u0026 avoid) {\n+\t\tlwsl_info(\u0022%s: allowing anyway\u005cn\u0022, __func__);\n \n \t\treturn 0;\n \t}\n+\n \tlws_snprintf(ebuf, ebuf_len,\n-\t\t\u0022server's cert didn't look good, (use_ssl 0x%x) X509_V_ERR \u003d %d: %s\u005cn\u0022,\n-\t\t(unsigned int)wsi-\u003etls.use_ssl, n, ERR_error_string((unsigned long)n, sb));\n+\t\t\u0022server's cert didn't look good, %s (use_ssl 0x%x) X509_V_ERR \u003d %d: %s\u005cn\u0022,\n+\t\ttype, (unsigned int)wsi-\u003etls.use_ssl, n,\n+\t\tERR_error_string((unsigned long)n, sb));\n+\n \tlwsl_info(\u0022%s\u005cn\u0022, ebuf);\n+\n \tlws_tls_err_describe_clear();\n \n \treturn -1;\ndiff --git a/lib/tls/mbedtls/mbedtls-ssl.c b/lib/tls/mbedtls/mbedtls-ssl.c\nindex 16fe226..2404806 100644\n--- a/lib/tls/mbedtls/mbedtls-ssl.c\n+++ b/lib/tls/mbedtls/mbedtls-ssl.c\n@@ -51,8 +51,6 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n \tif (!wsi-\u003etls.ssl)\n \t\treturn lws_ssl_capable_read_no_ssl(wsi, buf, len);\n \n-\tlws_stats_bump(pt, LWSSTATS_C_API_READ, 1);\n-\n \terrno \u003d 0;\n \tn \u003d SSL_read(wsi-\u003etls.ssl, buf, (int)len);\n #if defined(LWS_PLAT_FREERTOS)\n@@ -61,15 +59,6 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n \t\treturn LWS_SSL_CAPABLE_ERROR;\n \t}\n #endif\n-#if defined(LWS_WITH_STATS)\n-\tif (!wsi-\u003eseen_rx \u0026\u0026 wsi-\u003eaccept_start_us) {\n- lws_stats_bump(pt, LWSSTATS_US_SSL_RX_DELAY_AVG,\n-\t\t\tlws_now_usecs() - wsi-\u003eaccept_start_us);\n- lws_stats_bump(pt, LWSSTATS_C_SSL_CONNS_HAD_RX, 1);\n-\t\twsi-\u003eseen_rx \u003d 1;\n-\t}\n-#endif\n-\n \n \tlwsl_debug(\u0022%s: %s: SSL_read says %d\u005cn\u0022, __func__, lws_wsi_tag(wsi), n);\n \t/* manpage: returning 0 means connection shut down */\n@@ -82,14 +71,13 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n \tif (n \u003c 0) {\n \t\tm \u003d SSL_get_error(wsi-\u003etls.ssl, n);\n \t\tlwsl_debug(\u0022%s: %s: ssl err %d errno %d\u005cn\u0022, __func__, lws_wsi_tag(wsi), m, errno);\n-\t\tif (errno \u003d\u003d LWS_ENOTCONN) {\n+\t\tif (errno \u003d\u003d LWS_ENOTCONN)\n \t\t\t/* If the socket isn't connected anymore, bail out. */\n-\t\t\twsi-\u003esocket_is_permanently_unusable \u003d 1;\n-\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n-\t\t}\n+\t\t\tgoto do_err1;\n+\n \t\tif (m \u003d\u003d SSL_ERROR_ZERO_RETURN ||\n \t\t m \u003d\u003d SSL_ERROR_SYSCALL)\n-\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t\tgoto do_err;\n \n \t\tif (m \u003d\u003d SSL_ERROR_WANT_READ || SSL_want_read(wsi-\u003etls.ssl)) {\n \t\t\tlwsl_debug(\u0022%s: WANT_READ\u005cn\u0022, __func__);\n@@ -101,8 +89,16 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n \t\t\tlwsl_debug(\u0022%s: LWS_SSL_CAPABLE_MORE_SERVICE\u005cn\u0022, lws_wsi_tag(wsi));\n \t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE;\n \t\t}\n+\n+do_err1:\n \t\twsi-\u003esocket_is_permanently_unusable \u003d 1;\n \n+do_err:\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tif (wsi-\u003ea.vhost)\n+\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_rx, METRES_NOGO, 0);\n+#endif\n+\n \t\treturn LWS_SSL_CAPABLE_ERROR;\n \t}\n \n@@ -115,23 +111,12 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n \tlwsl_hexdump_notice(buf, n);\n #endif\n \n-\tlws_stats_bump(pt, LWSSTATS_B_READ, (uint64_t)n);\n-\n-#if defined(LWS_WITH_SERVER_STATUS)\n+#if defined(LWS_WITH_SYS_METRICS)\n \tif (wsi-\u003ea.vhost)\n-\t\twsi-\u003ea.vhost-\u003econn_stats.rx \u003d wsi-\u003ea.vhost-\u003econn_stats.rx + (unsigned long long)n;\n-#endif\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tif (context-\u003edetailed_latency_cb) {\n-\t\twsi-\u003edetlat.req_size \u003d len;\n-\t\twsi-\u003edetlat.acc_size \u003d n;\n-\t\twsi-\u003edetlat.type \u003d LDLT_READ;\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] \u003d\n-\t\t\tlws_now_usecs() - pt-\u003eust_left_poll;\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d 0;\n-\t\tlws_det_lat_cb(wsi-\u003ea.context, \u0026wsi-\u003edetlat);\n-\t}\n+\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_rx,\n+\t\t\t\t METRES_GO /* rx */, (u_mt_t)n);\n #endif\n+\n \t/*\n \t * if it was our buffer that limited what we read,\n \t * check if SSL has additional data pending inside SSL buffers.\n@@ -186,8 +171,14 @@ lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, size_t len)\n \t\treturn lws_ssl_capable_write_no_ssl(wsi, buf, len);\n \n \tn \u003d SSL_write(wsi-\u003etls.ssl, buf, (int)len);\n-\tif (n \u003e 0)\n+\tif (n \u003e 0) {\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tif (wsi-\u003ea.vhost)\n+\t\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_tx,\n+\t\t\t\t\t METRES_GO, (u_mt_t)n);\n+#endif\n \t\treturn n;\n+\t}\n \n \tm \u003d SSL_get_error(wsi-\u003etls.ssl, n);\n \tif (m !\u003d SSL_ERROR_SYSCALL) {\n@@ -208,6 +199,12 @@ lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, size_t len)\n \tlwsl_debug(\u0022%s failed: %d\u005cn\u0022,__func__, m);\n \twsi-\u003esocket_is_permanently_unusable \u003d 1;\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tif (wsi-\u003ea.vhost)\n+\t\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_tx,\n+\t\t\t\t\t METRES_NOGO, (u_mt_t)n);\n+#endif\n+\n \treturn LWS_SSL_CAPABLE_ERROR;\n }\n \ndiff --git a/lib/tls/openssl/openssl-client.c b/lib/tls/openssl/openssl-client.c\nindex 1c6e08a..590ed86 100644\n--- a/lib/tls/openssl/openssl-client.c\n+++ b/lib/tls/openssl/openssl-client.c\n@@ -125,6 +125,18 @@ OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)\n \t\t\tlwsl_err(\u0022SSL error: %s (preverify_ok\u003d%d;err\u003d%d;\u0022\n \t\t\t\t \u0022depth\u003d%d)\u005cn\u0022, msg, preverify_ok, err, depth);\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\t\t{\n+\t\t\t\tchar buckname[64];\n+\n+\t\t\t\tlws_snprintf(buckname, sizeof(buckname),\n+\t\t\t\t\t \u0022tls\u003d\u005c\u0022%s\u005c\u0022\u0022, msg);\n+\t\t\t\tlws_metrics_hist_bump_describe_wsi(wsi,\n+\t\t\t\t\tlws_metrics_priv_to_pub(wsi-\u003ea.context-\u003emth_conn_failures),\n+\t\t\t\t\tbuckname);\n+\t\t\t}\n+#endif\n+\n \t\t\treturn preverify_ok;\t// not ok\n \t\t}\n \t}\n@@ -480,7 +492,8 @@ lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, size_t ebuf_len)\n #if !defined(USE_WOLFSSL)\n \tstruct lws_context_per_thread *pt \u003d \u0026wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi];\n \tchar *p \u003d (char *)\u0026pt-\u003eserv_buf[0];\n-\tconst char *es;\n+\tconst char *es, *type \u003d \u0022\u0022;\n+\tunsigned int avoid \u003d 0;\n \tchar *sb \u003d p;\n \tlong n;\n \n@@ -488,29 +501,46 @@ lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, size_t ebuf_len)\n \tERR_clear_error();\n \tn \u003d SSL_get_verify_result(wsi-\u003etls.ssl);\n \n-\tlwsl_debug(\u0022get_verify says %ld\u005cn\u0022, n);\n-\n-\tif (n \u003d\u003d X509_V_OK)\n+\tswitch (n) {\n+\tcase X509_V_OK:\n \t\treturn 0;\n \n-\tif ((n \u003d\u003d X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||\n-\t n \u003d\u003d X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) \u0026\u0026\n-\t (wsi-\u003etls.use_ssl \u0026 LCCSCF_ALLOW_SELFSIGNED)) {\n-\t\tlwsl_info(\u0022accepting self-signed certificate\u005cn\u0022);\n-\n-\t\treturn 0;\n-\t}\n-\tif ((n \u003d\u003d X509_V_ERR_CERT_NOT_YET_VALID ||\n-\t n \u003d\u003d X509_V_ERR_CERT_HAS_EXPIRED) \u0026\u0026\n-\t (wsi-\u003etls.use_ssl \u0026 LCCSCF_ALLOW_EXPIRED)) {\n-\t\tlwsl_info(\u0022accepting expired certificate\u005cn\u0022);\n-\t\treturn 0;\n+\tcase X509_V_ERR_HOSTNAME_MISMATCH:\n+\t\ttype \u003d \u0022tls\u003dhostname\u0022;\n+\t\tavoid \u003d LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;\n+\t\tbreak;\n+\n+\tcase X509_V_ERR_INVALID_CA:\n+\tcase X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:\n+\tcase X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:\n+\t\ttype \u003d \u0022tls\u003dinvalidca\u0022;\n+\t\tavoid \u003d LCCSCF_ALLOW_SELFSIGNED;\n+\t\tbreak;\n+\n+\tcase X509_V_ERR_CERT_NOT_YET_VALID:\n+\t\ttype \u003d \u0022tls\u003dnotyetvalid\u0022;\n+\t\tavoid \u003d LCCSCF_ALLOW_EXPIRED;\n+\t\tbreak;\n+\n+\tcase X509_V_ERR_CERT_HAS_EXPIRED:\n+\t\ttype \u003d \u0022tls\u003dexpired\u0022;\n+\t\tavoid \u003d LCCSCF_ALLOW_EXPIRED;\n+\t\tbreak;\n \t}\n-\tif (n \u003d\u003d X509_V_ERR_CERT_NOT_YET_VALID) {\n-\t\tlwsl_info(\u0022Cert is from the future... \u0022\n-\t\t\t \u0022probably our clock... accepting...\u005cn\u0022);\n+\n+\tlwsl_info(\u0022%s: cert problem: %s\u005cn\u0022, __func__, type);\n+\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tlws_metrics_hist_bump_describe_wsi(wsi,\n+\t\t\tlws_metrics_priv_to_pub(wsi-\u003ea.context-\u003emth_conn_failures), type);\n+#endif\n+\n+\tif (wsi-\u003etls.use_ssl \u0026 avoid) {\n+\t\tlwsl_info(\u0022%s: allowing anyway\u005cn\u0022, __func__);\n+\n \t\treturn 0;\n \t}\n+\n \tes \u003d ERR_error_string(\n \t#if defined(LWS_WITH_BORINGSSL)\n \t\t\t\t\t (uint32_t)\n@@ -519,8 +549,8 @@ lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, size_t ebuf_len)\n \t#endif\n \t\t\t\t\t n, sb);\n \tlws_snprintf(ebuf, ebuf_len,\n-\t\t\u0022server's cert didn't look good, X509_V_ERR \u003d %ld: %s\u005cn\u0022,\n-\t\t n, es);\n+\t\t\u0022server's cert didn't look good, %s X509_V_ERR \u003d %ld: %s\u005cn\u0022,\n+\t\t type, n, es);\n \tlwsl_info(\u0022%s\u005cn\u0022, ebuf);\n \tlws_tls_err_describe_clear();\n \ndiff --git a/lib/tls/openssl/openssl-ssl.c b/lib/tls/openssl/openssl-ssl.c\nindex a593d87..562c352 100644\n--- a/lib/tls/openssl/openssl-ssl.c\n+++ b/lib/tls/openssl/openssl-ssl.c\n@@ -207,8 +207,6 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n \tif (!wsi-\u003etls.ssl)\n \t\treturn lws_ssl_capable_read_no_ssl(wsi, buf, len);\n \n-\tlws_stats_bump(pt, LWSSTATS_C_API_READ, 1);\n-\n \terrno \u003d 0;\n \tERR_clear_error();\n \tn \u003d SSL_read(wsi-\u003etls.ssl, buf, (int)(ssize_t)len);\n@@ -218,16 +216,6 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n \t\treturn LWS_SSL_CAPABLE_ERROR;\n \t}\n #endif\n-#if defined(LWS_WITH_STATS)\n-\tif (!wsi-\u003eseen_rx \u0026\u0026 wsi-\u003eaccept_start_us) {\n- lws_stats_bump(pt, LWSSTATS_US_SSL_RX_DELAY_AVG,\n- \t\t lws_now_usecs() -\n- \t\t\t wsi-\u003eaccept_start_us);\n- lws_stats_bump(pt, LWSSTATS_C_SSL_CONNS_HAD_RX, 1);\n-\t\twsi-\u003eseen_rx \u003d 1;\n-\t}\n-#endif\n-\n \n \tlwsl_debug(\u0022%s: SSL_read says %d\u005cn\u0022, lws_wsi_tag(wsi), n);\n \t/* manpage: returning 0 means connection shut down\n@@ -258,7 +246,7 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n \t\tm \u003d lws_ssl_get_error(wsi, n);\n \t\tlwsl_debug(\u0022%s: ssl err %d errno %d\u005cn\u0022, lws_wsi_tag(wsi), m, errno);\n \t\tif (m \u003d\u003d SSL_ERROR_ZERO_RETURN) /* cleanly shut down */\n-\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t\tgoto do_err;\n \n \t\t/* hm not retryable.. could be 0 size pkt or error */\n \n@@ -268,7 +256,12 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n \t\t\t/* unclean, eg closed conn */\n \n \t\t\twsi-\u003esocket_is_permanently_unusable \u003d 1;\n-\n+do_err:\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tif (wsi-\u003ea.vhost)\n+\t\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_rx,\n+\t\t\t\t\t METRES_NOGO, 0);\n+#endif\n \t\t\treturn LWS_SSL_CAPABLE_ERROR;\n \t\t}\n \n@@ -294,26 +287,12 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n \t * paths to dump what was received as decrypted data from the tls tunnel\n \t */\n \tlwsl_notice(\u0022%s: len %d\u005cn\u0022, __func__, n);\n-\tlwsl_hexdump_notice(buf, n);\n+\tlwsl_hexdump_notice(buf, (unsigned int)n);\n #endif\n \n-\tlws_stats_bump(pt, LWSSTATS_B_READ, (unsigned int)n);\n-\n-#if defined(LWS_WITH_SERVER_STATUS)\n+#if defined(LWS_WITH_SYS_METRICS)\n \tif (wsi-\u003ea.vhost)\n-\t\twsi-\u003ea.vhost-\u003econn_stats.rx \u003d (unsigned long long)(wsi-\u003ea.vhost-\u003econn_stats.rx + (unsigned long long)(long long)n);\n-#endif\n-\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tif (context-\u003edetailed_latency_cb) {\n-\t\twsi-\u003edetlat.req_size \u003d len;\n-\t\twsi-\u003edetlat.acc_size \u003d (unsigned int)n;\n-\t\twsi-\u003edetlat.type \u003d LDLT_READ;\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] \u003d\n-\t\t\t(uint32_t)(lws_now_usecs() - pt-\u003eust_left_poll);\n-\t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d 0;\n-\t\tlws_det_lat_cb(wsi-\u003ea.context, \u0026wsi-\u003edetlat);\n-\t}\n+\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_rx, METRES_GO, (u_mt_t)n);\n #endif\n \n \t/*\n@@ -363,7 +342,7 @@ lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, size_t len)\n \t * paths before sending data into the tls tunnel, where you can dump it\n \t * and see what is being sent.\n \t */\n-\tlwsl_notice(\u0022%s: len %d\u005cn\u0022, __func__, len);\n+\tlwsl_notice(\u0022%s: len %u\u005cn\u0022, __func__, (unsigned int)len);\n \tlwsl_hexdump_notice(buf, len);\n #endif\n \n@@ -373,8 +352,14 @@ lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, size_t len)\n \terrno \u003d 0;\n \tERR_clear_error();\n \tn \u003d SSL_write(wsi-\u003etls.ssl, buf, (int)(ssize_t)len);\n-\tif (n \u003e 0)\n+\tif (n \u003e 0) {\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tif (wsi-\u003ea.vhost)\n+\t\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_tx,\n+\t\t\t\t\t METRES_GO, (u_mt_t)n);\n+#endif\n \t\treturn n;\n+\t}\n \n \tm \u003d lws_ssl_get_error(wsi, n);\n \tif (m !\u003d SSL_ERROR_SYSCALL) {\n@@ -398,6 +383,12 @@ lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, size_t len)\n \n \twsi-\u003esocket_is_permanently_unusable \u003d 1;\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\t\tif (wsi-\u003ea.vhost)\n+\t\t\tlws_metric_event(wsi-\u003ea.vhost-\u003emt_traffic_tx,\n+\t\t\t\t\t METRES_NOGO, 0);\n+#endif\n+\n \treturn LWS_SSL_CAPABLE_ERROR;\n }\n \ndiff --git a/lib/tls/tls-client.c b/lib/tls/tls-client.c\nindex b8f7a51..e576f8c 100644\n--- a/lib/tls/tls-client.c\n+++ b/lib/tls/tls-client.c\n@@ -34,6 +34,7 @@ lws_ssl_client_connect1(struct lws *wsi, char *errbuf, size_t len)\n \tcase LWS_SSL_CAPABLE_ERROR:\n \t\treturn -1;\n \tcase LWS_SSL_CAPABLE_DONE:\n+\t\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_GO);\n \t\treturn 1; /* connected */\n \tcase LWS_SSL_CAPABLE_MORE_SERVICE_WRITE:\n \t\tlws_callback_on_writable(wsi);\n@@ -73,8 +74,12 @@ lws_ssl_client_connect2(struct lws *wsi, char *errbuf, size_t len)\n \t\t}\n \t}\n \n-\tif (lws_tls_client_confirm_peer_cert(wsi, errbuf, len))\n+\tif (lws_tls_client_confirm_peer_cert(wsi, errbuf, len)) {\n+\t\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_NOGO);\n \t\treturn -1;\n+\t}\n+\n+\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_GO);\n \n \treturn 1;\n }\n@@ -187,6 +192,9 @@ lws_client_create_tls(struct lws *wsi, const char **pcce, int do_c1)\n \t\tif (!do_c1)\n \t\t\treturn 0;\n \n+\t\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_GO);\n+\t\tlws_metrics_caliper_bind(wsi-\u003ecal_conn, wsi-\u003ea.context-\u003emt_conn_tls);\n+\n \t\tn \u003d lws_ssl_client_connect1(wsi, (char *)wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi].serv_buf,\n \t\t\t\t\t wsi-\u003ea.context-\u003ept_serv_buf_size);\n \t\tlwsl_debug(\u0022%s: lws_ssl_client_connect1: %d\u005cn\u0022, __func__, n);\n@@ -194,8 +202,10 @@ lws_client_create_tls(struct lws *wsi, const char **pcce, int do_c1)\n \t\t\treturn CCTLS_RETURN_RETRY; /* caller should return 0 */\n \t\tif (n \u003c 0) {\n \t\t\t*pcce \u003d (const char *)wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi].serv_buf;\n+\t\t\tlws_metrics_caliper_report(wsi-\u003ecal_conn, METRES_NOGO);\n \t\t\treturn CCTLS_RETURN_ERROR;\n \t\t}\n+\t\t/* ...connect1 already handled caliper if SSL_accept done */\n \t} else\n \t\twsi-\u003etls.ssl \u003d NULL;\n \ndiff --git a/lib/tls/tls-network.c b/lib/tls/tls-network.c\nindex 5deab5b..c90faa7 100644\n--- a/lib/tls/tls-network.c\n+++ b/lib/tls/tls-network.c\n@@ -204,10 +204,6 @@ lws_gate_accepts(struct lws_context *context, int on)\n \n \tlwsl_notice(\u0022%s: on \u003d %d\u005cn\u0022, __func__, on);\n \n-#if defined(LWS_WITH_STATS)\n-\tcontext-\u003eupdated \u003d 1;\n-#endif\n-\n \twhile (v) {\n \t\tif (v-\u003etls.use_ssl \u0026\u0026 v-\u003elserv_wsi \u0026\u0026\n \t\t lws_change_pollfd(v-\u003elserv_wsi, (LWS_POLLIN) * !on,\ndiff --git a/lib/tls/tls-server.c b/lib/tls/tls-server.c\nindex c4983df..887159f 100644\n--- a/lib/tls/tls-server.c\n+++ b/lib/tls/tls-server.c\n@@ -157,9 +157,6 @@ lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd, char f\n \t\t\tgoto fail;\n \t\t}\n \n-#if defined(LWS_WITH_STATS)\n-\t\tcontext-\u003eupdated \u003d 1;\n-#endif\n \t\t/*\n \t\t * we are not accepted yet, but we need to enter ourselves\n \t\t * as a live connection. That way we can retry when more\n@@ -318,20 +315,13 @@ punt:\n \n \t\t/* normal SSL connection processing path */\n \n-#if defined(LWS_WITH_STATS)\n-\t\t/* only set this the first time around */\n-\t\tif (!wsi-\u003eaccept_start_us)\n-\t\t\twsi-\u003eaccept_start_us \u003d lws_now_usecs();\n-#endif\n \t\terrno \u003d 0;\n-\t\tlws_stats_bump(pt, LWSSTATS_C_SSL_ACCEPT_SPIN, 1);\n \t\tn \u003d lws_tls_server_accept(wsi);\n \t\tlwsl_info(\u0022SSL_accept says %d\u005cn\u0022, n);\n \t\tswitch (n) {\n \t\tcase LWS_SSL_CAPABLE_DONE:\n \t\t\tbreak;\n \t\tcase LWS_SSL_CAPABLE_ERROR:\n-\t\t\tlws_stats_bump(pt, LWSSTATS_C_SSL_CONNECTIONS_FAILED, 1);\n \t lwsl_info(\u0022%s: SSL_accept failed socket %u: %d\u005cn\u0022,\n \t \t\t__func__, wsi-\u003edesc.sockfd, n);\n \t\t\twsi-\u003esocket_is_permanently_unusable \u003d 1;\n@@ -341,26 +331,6 @@ punt:\n \t\t\treturn 0;\n \t\t}\n \n-\t\tlws_stats_bump(pt, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, 1);\n-#if defined(LWS_WITH_STATS)\n-\t\tif (wsi-\u003eaccept_start_us)\n-\t\t\tlws_stats_bump(pt,\n-\t\t\t\t LWSSTATS_US_SSL_ACCEPT_LATENCY_AVG,\n-\t\t\t\t lws_now_usecs() -\n-\t\t\t\t\t wsi-\u003eaccept_start_us);\n-\t\twsi-\u003eaccept_start_us \u003d lws_now_usecs();\n-#endif\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\t\tif (context-\u003edetailed_latency_cb) {\n-\t\t\twsi-\u003edetlat.type \u003d LDLT_TLS_NEG_SERVER;\n-\t\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] \u003d\n-\t\t\t\t(uint32_t)(lws_now_usecs() -\n-\t\t\t\twsi-\u003edetlat.earliest_write_req_pre_write);\n-\t\t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d 0;\n-\t\t\tlws_det_lat_cb(wsi-\u003ea.context, \u0026wsi-\u003edetlat);\n-\t\t}\n-#endif\n-\n \t\t/* adapt our vhost to match the SNI SSL_CTX that was chosen */\n \t\tvh \u003d context-\u003evhost_list;\n \t\twhile (vh) {\ndiff --git a/lwsws/main.c b/lwsws/main.c\nindex d2b70b7..eb19a04 100644\n--- a/lwsws/main.c\n+++ b/lwsws/main.c\n@@ -64,7 +64,7 @@ static uv_signal_t signal_outer[2];\n static int pids[32];\n void lwsl_emit_stderr(int level, const char *line);\n \n-#define LWSWS_CONFIG_STRING_SIZE (32 * 1024)\n+#define LWSWS_CONFIG_STRING_SIZE (64 * 1024)\n \n static const struct lws_extension exts[] \u003d {\n #if !defined(LWS_WITHOUT_EXTENSIONS)\ndiff --git a/minimal-examples/api-tests/api-test-lws_tokenize/main.c b/minimal-examples/api-tests/api-test-lws_tokenize/main.c\nindex b556789..d69dc2f 100644\n--- a/minimal-examples/api-tests/api-test-lws_tokenize/main.c\n+++ b/minimal-examples/api-tests/api-test-lws_tokenize/main.c\n@@ -204,7 +204,7 @@ struct tests tests[] \u003d {\n \t}, {\n \t\t\u0022fr-CH, fr;q\u003d0.9, en;q\u003d0.8, de;q\u003d0.7, *;q\u003d0.5\u0022,\n \t\texpected7, LWS_ARRAY_SIZE(expected7),\n-\t\tLWS_TOKENIZE_F_RFC7230_DELIMS\n+\t\tLWS_TOKENIZE_F_ASTERISK_NONTERM | LWS_TOKENIZE_F_RFC7230_DELIMS\n \t},\n \t{\n \t\t\u0022 Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι, greek\u0022,\n@@ -720,6 +720,10 @@ int main(int argc, const char **argv)\n \t\tlwsl_user(\u0022%s: wc 15 fail\u005cn\u0022, __func__);\n \t\tfail++;\n \t}\n+\tif (lws_strcmp_wildcard(\u0022ssproxy.n.cn.*\u0022, 14, \u0022ssproxy.n.cn.failures\u0022)) {\n+\t\tlwsl_user(\u0022%s: wc 16 fail\u005cn\u0022, __func__);\n+\t\tfail++;\n+\t}\n \n \tlwsl_user(\u0022Completed: PASS: %d, FAIL: %d\u005cn\u0022, ok, fail);\n \ndiff --git a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c\nindex 626e280..8a447f2 100644\n--- a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c\n+++ b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c\n@@ -217,6 +217,30 @@ static const struct lws_protocols protocols[] \u003d {\n \t{ NULL, NULL, 0, 0 }\n };\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\n+static int\n+my_metric_report(lws_metric_pub_t *mp)\n+{\n+\tlws_metric_bucket_t *sub \u003d mp-\u003eu.hist.head;\n+\tchar buf[192];\n+\n+\tdo {\n+\t\tif (lws_metrics_format(mp, \u0026sub, buf, sizeof(buf)))\n+\t\t\tlwsl_user(\u0022%s: %s\u005cn\u0022, __func__, buf);\n+\t} while ((mp-\u003eflags \u0026 LWSMTFL_REPORT_HIST) \u0026\u0026 sub);\n+\n+\t/* 0 \u003d leave metric to accumulate, 1 \u003d reset the metric */\n+\n+\treturn 1;\n+}\n+\n+static const lws_system_ops_t system_ops \u003d {\n+\t.metric_report \u003d my_metric_report,\n+};\n+\n+#endif\n+\n static void\n signal_cb(void *handle, int signum)\n {\n@@ -362,6 +386,11 @@ int main(int argc, const char **argv)\n \t * network wsi) that we will use.\n \t */\n \tinfo.fd_limit_per_thread \u003d 1 + COUNT + 1;\n+\tinfo.pcontext \u003d \u0026context;\n+\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tinfo.system_ops \u003d \u0026system_ops;\n+#endif\n \n #if defined(LWS_WITH_MBEDTLS) || defined(USE_WOLFSSL)\n \t/*\n@@ -374,11 +403,6 @@ int main(int argc, const char **argv)\n \tif ((p \u003d lws_cmdline_option(argc, argv, \u0022--limit\u0022)))\n \t\tinfo.simultaneous_ssl_restriction \u003d atoi(p);\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n-\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-results\u0022;\n-#endif\n-\n \tcontext \u003d lws_create_context(\u0026info);\n \tif (!context) {\n \t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\ndiff --git a/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c b/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c\nindex dbc4e72..0049980 100644\n--- a/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c\n+++ b/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c\n@@ -23,8 +23,41 @@\n \n static int interrupted;\n \n-static const struct lws_http_mount mount \u003d {\n+#if defined(LWS_WITH_PLUGINS)\n+static const char * const plugin_dirs[] \u003d {\n+\tLWS_INSTALL_DATADIR\u0022/libwebsockets-test-server/plugins/\u0022,\n+\tNULL\n+};\n+#endif\n+\n+static const struct lws_http_mount\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tmount_metrics \u003d {\n \t/* .mount_next */\t\tNULL,\t\t/* linked-list \u0022next\u0022 */\n+\t/* .mountpoint */\t\t\u0022/metrics\u0022,\t\t/* mountpoint URL */\n+\t/* .origin */\t\t\t\u0022lws-openmetrics\u0022, /* serve from dir */\n+\t/* .def */\t\t\t\u0022x\u0022,\t/* default filename */\n+\t/* .protocol */\t\t\t\u0022lws-openmetrics\u0022,\n+\t/* .cgienv */\t\t\tNULL,\n+\t/* .extra_mimetypes */\t\tNULL,\n+\t/* .interpret */\t\tNULL,\n+\t/* .cgi_timeout */\t\t0,\n+\t/* .cache_max_age */\t\t0,\n+\t/* .auth_mask */\t\t0,\n+\t/* .cache_reusable */\t\t0,\n+\t/* .cache_revalidate */\t\t0,\n+\t/* .cache_intermediaries */\t0,\n+\t/* .origin_protocol */\t\tLWSMPRO_CALLBACK, /* bind to callback */\n+\t/* .mountpoint_len */\t\t8,\t\t/* char count */\n+\t/* .basic_auth_login_file */\tNULL,\n+\t},\n+#endif\n+\tmount \u003d {\n+#if defined(LWS_WITH_SYS_METRICS)\n+\t/* .mount_next */\t\t\u0026mount_metrics,\t\t/* linked-list \u0022next\u0022 */\n+#else\n+\t/* .mount_next */\t\tNULL,\t\t/* linked-list \u0022next\u0022 */\n+#endif\n \t/* .mountpoint */\t\t\u0022/\u0022,\t\t/* mountpoint URL */\n \t/* .origin */\t\t\t\u0022./mount-origin\u0022, /* serve from dir */\n \t/* .def */\t\t\t\u0022index.html\u0022,\t/* default filename */\n@@ -94,6 +127,10 @@ int main(int argc, const char **argv)\n \tinfo.ssl_cert_filepath \u003d \u0022localhost-100y.cert\u0022;\n \tinfo.ssl_private_key_filepath \u003d \u0022localhost-100y.key\u0022;\n \n+#if defined(LWS_WITH_PLUGINS)\n+\tinfo.plugin_dirs \u003d plugin_dirs;\n+#endif\n+\n \tif (lws_cmdline_option(argc, argv, \u0022-h\u0022))\n \t\tinfo.options |\u003d LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK;\n \ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c b/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c\nindex d07c4c7..f6a24d7 100644\n--- a/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c\n@@ -389,11 +389,6 @@ int main(int argc, const char **argv)\n \tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n \tinfo.pprotocols \u003d protocols;\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n-\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n-#endif\n-\n \t/* integrate us with lws system state management when context created */\n \tnl.name \u003d \u0022app\u0022;\n \tnl.notify_cb \u003d app_system_state_nf;\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/main-client.c b/minimal-examples/secure-streams/minimal-secure-streams-avs/main-client.c\nindex bb9b894..0f62d43 100644\n--- a/minimal-examples/secure-streams/minimal-secure-streams-avs/main-client.c\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/main-client.c\n@@ -100,11 +100,6 @@ int main(int argc, const char **argv)\n \tinfo.protocols \u003d lws_sspc_protocols;\n \tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n-\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n-#endif\n-\n \t/* integrate us with lws system state management when context created */\n \tnl.name \u003d \u0022app\u0022;\n \tnl.notify_cb \u003d app_system_state_nf;\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c b/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c\nindex b3b18f6..56ca531 100644\n--- a/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c\n@@ -341,11 +341,6 @@ int main(int argc, const char **argv)\n \t}\n #endif\n \n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n-\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n-#endif\n-\n \t/* integrate us with lws system state management when context created */\n \tnl.name \u003d \u0022app\u0022;\n \tnl.notify_cb \u003d app_system_state_nf;\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-metrics-proxy/metrics-proxy-policy.json b/minimal-examples/secure-streams/minimal-secure-streams-metrics-proxy/metrics-proxy-policy.json\nnew file mode 100644\nindex 0000000..078b025\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-metrics-proxy/metrics-proxy-policy.json\n@@ -0,0 +1,59 @@\n+{\n+\t\u0022release\u0022:\u002201234567\u0022,\n+\t\u0022product\u0022:\u0022myproduct\u0022,\n+\t\u0022schema-version\u0022:1,\n+\t\u0022retry\u0022: [{\n+\t\t\u0022default\u0022: {\n+\t\t\t\u0022backoff\u0022: [1000,2000,3000,5000,10000],\n+\t\t\t\u0022conceal\u0022:5,\n+\t\t\t\u0022jitterpc\u0022:20,\n+\t\t\t\u0022svalidping\u0022:300,\n+\t\t\t\u0022svalidhup\u0022:310\n+\t\t}}],\n+\t\u0022certs\u0022: [{\n+\t\t\u0022dst_root_x3\u0022: \u0022MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\u0022},{\u0022self_localhost\u0022: \u0022MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuWaICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXarjr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrowYNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuAxbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9PwtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjvxQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKkujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYAAOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6GgmnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIXe2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE\u003d\u0022},{\u0022self_localhost_key\u0022: \u0022MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8fqokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5AKqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMTG+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXglxBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvsesnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqwzFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVzmgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCwau9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN7740QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFHPgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXjW7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuRnaVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr62ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDCR1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMpY+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaChBVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCEfXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQx1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHIUlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RMOMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/AaJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6Sme/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+IG4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iKTncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMrZiw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3ENqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrsfBrpEY1IATtPq1taBZZogRqI3rOkkPk\u003d\u0022\n+\t}],\n+\t\u0022trust_stores\u0022: [\n+\t\t{\u0022name\u0022: \u0022le_via_dst\u0022,\n+\t\t \u0022stack\u0022: [\u0022dst_root_x3\u0022]\n+\t\t}\n+\t], \u0022s\u0022: [\n+\t\t{\n+\t\t\t\u0022mintest\u0022: {\n+\t\t\t\t\u0022endpoint\u0022:\u0022warmcat.com\u0022,\n+\t\t\t\t\u0022port\u0022:443,\n+\t\t\t\t\u0022protocol\u0022:\u0022h2\u0022,\n+\t\t\t\t\u0022http_method\u0022:\u0022GET\u0022,\n+\t\t\t\t\u0022http_url\u0022:\u0022index.html\u0022,\n+\t\t\t\t\u0022tls\u0022:true,\n+\t\t\t\t\u0022retry\u0022:\u0022default\u0022,\n+\t\t\t\t\u0022tls_trust_store\u0022:\u0022le_via_dst\u0022\n+\t\t}},{\n+\t\t\t\u0022forscraper\u0022: {\n+\t\t\t\t\u0022server\u0022:true,\n+\t\t\t\t\u0022port\u0022:19090,\n+\t\t\t\t\u0022protocol\u0022:\u0022h1\u0022,\n+\t\t\t\t\u0022metadata\u0022: [{\n+\t\t\t\t\t\u0022mime\u0022: \u0022Content-Type:\u0022,\n+\t\t\t\t\t\u0022method\u0022: \u0022\u0022,\n+\t\t\t\t\t\u0022path\u0022: \u0022\u0022\n+\t\t\t\t}\n+\t\t\t]\n+\t\t}},{\n+\t\t\t\u0022forclients\u0022: {\n+\t\t\t\t\u0022server\u0022:true,\n+\t\t\t\t\u0022port\u0022:19091,\n+\t\t\t\t\u0022protocol\u0022:\u0022h1\u0022,\n+\t\t\t\t\u0022metadata\u0022: [{\n+\t\t\t\t\t\u0022mime\u0022: \u0022Content-Type:\u0022,\n+\t\t\t\t\t\u0022method\u0022: \u0022\u0022,\n+\t\t\t\t\t\u0022path\u0022: \u0022\u0022\n+\t\t\t\t}],\n+\t\t\t\t\u0022tls\u0022:true,\n+\t\t\t\t\u0022ws_subprotocol\u0022:\u0022lws-metrics-proxy\u0022,\n+\t\t\t\t\u0022server_cert\u0022:\u0022self_localhost\u0022,\n+\t\t\t\t\u0022server_key\u0022:\u0022self_localhost_key\u0022\n+\t\t}}\n+\t]\n+}\n+\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-post/minimal-secure-streams-post.c b/minimal-examples/secure-streams/minimal-secure-streams-post/minimal-secure-streams-post.c\nindex 8a649ad..a213135 100644\n--- a/minimal-examples/secure-streams/minimal-secure-streams-post/minimal-secure-streams-post.c\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-post/minimal-secure-streams-post.c\n@@ -500,10 +500,6 @@ int main(int argc, const char **argv)\n \tinfo.options \u003d LWS_SERVER_OPTION_EXPLICIT_VHOSTS |\n \t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n #endif\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n-\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n-#endif\n \n \t/* integrate us with lws system state management when context created */\n \ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c\nindex b8f428f..122cf7c 100644\n--- a/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c\n@@ -206,6 +206,30 @@ static lws_state_notify_link_t * const app_notifier_list[] \u003d {\n \t\u0026nl, NULL\n };\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\n+static int\n+my_metric_report(lws_metric_pub_t *mp)\n+{\n+\tlws_metric_bucket_t *sub \u003d mp-\u003eu.hist.head;\n+\tchar buf[192];\n+\n+\tdo {\n+\t\tif (lws_metrics_format(mp, \u0026sub, buf, sizeof(buf)))\n+\t\t\tlwsl_user(\u0022%s: %s\u005cn\u0022, __func__, buf);\n+\t} while ((mp-\u003eflags \u0026 LWSMTFL_REPORT_HIST) \u0026\u0026 sub);\n+\n+\t/* 0 \u003d leave metric to accumulate, 1 \u003d reset the metric */\n+\n+\treturn 1;\n+}\n+\n+static const lws_system_ops_t system_ops \u003d {\n+\t.metric_report \u003d my_metric_report,\n+};\n+\n+#endif\n+\n static void\n sigint_handler(int sig)\n {\n@@ -262,13 +286,9 @@ int main(int argc, const char **argv)\n \tinfo.options \u003d LWS_SERVER_OPTION_EXPLICIT_VHOSTS |\n \t\t LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW |\n \t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n-\tinfo.fd_limit_per_thread \u003d 1 + 32 + 1;\n+\tinfo.fd_limit_per_thread \u003d 1 + 6 + 1;\n \tinfo.pss_policies_json \u003d default_ss_policy;\n \tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n-\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n-#endif\n \n \t/* integrate us with lws system state management when context created */\n \tnl.name \u003d \u0022app\u0022;\n@@ -278,6 +298,11 @@ int main(int argc, const char **argv)\n \tinfo.pt_serv_buf_size \u003d (unsigned int)((6144 * 2) + 2048);\n \tinfo.max_http_header_data \u003d (unsigned short)(6144 + 2048);\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tinfo.system_ops \u003d \u0026system_ops;\n+\tinfo.metrics_prefix \u003d \u0022ssproxy\u0022;\n+#endif\n+\n \tcontext \u003d lws_create_context(\u0026info);\n \tif (!context) {\n \t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-smd/minimal-secure-streams-smd.c b/minimal-examples/secure-streams/minimal-secure-streams-smd/minimal-secure-streams-smd.c\nindex a0d0286..741e693 100644\n--- a/minimal-examples/secure-streams/minimal-secure-streams-smd/minimal-secure-streams-smd.c\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-smd/minimal-secure-streams-smd.c\n@@ -178,6 +178,7 @@ static const lws_ss_info_t ssi_lws_smd \u003d {\n \t.user_alloc\t\t \u003d sizeof(myss_t),\n \t.streamtype\t\t \u003d LWS_SMD_STREAMTYPENAME,\n \t.manual_initial_tx_credit \u003d LWSSMDCL_SYSTEM_STATE |\n+\t\t\t\t LWSSMDCL_METRICS |\n \t\t\t\t LWSSMDCL_NETWORK,\n };\n \ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-staticpolicy/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams-staticpolicy/minimal-secure-streams.c\nindex d276c21..1cffe36 100644\n--- a/minimal-examples/secure-streams/minimal-secure-streams-staticpolicy/minimal-secure-streams.c\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-staticpolicy/minimal-secure-streams.c\n@@ -230,10 +230,6 @@ int main(int argc, const char **argv)\n \tinfo.options \u003d LWS_SERVER_OPTION_EXPLICIT_VHOSTS |\n \t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n #endif\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n-\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n-#endif\n \n \t/* integrate us with lws system state management when context created */\n \ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-testsfail/minimal-secure-streams-testsfail.c b/minimal-examples/secure-streams/minimal-secure-streams-testsfail/minimal-secure-streams-testsfail.c\nindex 9587684..e5339e1 100644\n--- a/minimal-examples/secure-streams/minimal-secure-streams-testsfail/minimal-secure-streams-testsfail.c\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-testsfail/minimal-secure-streams-testsfail.c\n@@ -350,6 +350,58 @@ static const char * const default_ss_policy \u003d\n \t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022: true,\u0022\n \t\t\t\u0022\u005c\u0022retry\u005c\u0022: \u005c\u0022default\u005c\u0022,\u0022\n \t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022: \u005c\u0022arca1\u005c\u0022\u0022\n+\n+\t\t\u0022}},{\u0022\n+\n+\t\t/*\n+\t\t * Various kinds of tls failure\n+\t\t *\n+\t\t * hostname.badcert.warmcat.com: serves valid cert but for\n+\t\t *\t\t\t\t warmcat.com\n+\t\t *\n+\t\t * warmcat.com:446: serves valid but expired cert\n+\t\t *\n+\t\t * I don't have an easy way to make the test for \u0022not valid yet\u0022\n+\t\t * cert without root\n+\t\t *\n+\t\t * invalidca.badcert.warmcat.com: selfsigned cert for that\n+\t\t *\t\t\t\t hostname\n+\t\t */\n+\n+\t\t \u0022\u005c\u0022badcert_hostname\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022: \u005c\u0022hostname.badcert.warmcat.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022: 443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022: \u005c\u0022h1\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022: \u005c\u0022GET\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022: \u005c\u0022/\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022: true,\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022: true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022: \u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022: \u005c\u0022le_via_dst\u005c\u0022\u0022\n+\t\t\u0022}},{\u0022\n+\t\t \u0022\u005c\u0022badcert_expired\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022: \u005c\u0022warmcat.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022: 446,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022: \u005c\u0022h1\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022: \u005c\u0022GET\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022: \u005c\u0022/\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022: true,\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022: true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022: \u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022: \u005c\u0022le_via_dst\u005c\u0022\u0022\n+\t\t\u0022}},{\u0022\n+\t\t \u0022\u005c\u0022badcert_selfsigned\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022: \u005c\u0022invalidca.badcert.warmcat.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022: 443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022: \u005c\u0022h1\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022: \u005c\u0022GET\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022: \u005c\u0022/\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022: true,\u0022\n+\t\t\t\u0022\u005c\u0022nghttp2_quirk_end_stream\u005c\u0022: true,\u0022\n+\t\t\t\u0022\u005c\u0022h2q_oflow_txcr\u005c\u0022: true,\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022: true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022: \u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022: \u005c\u0022le_via_dst\u005c\u0022\u0022\n \u0022}}\u0022\n \t\u0022]}\u0022\n ;\n@@ -495,6 +547,29 @@ struct tests_seq {\n \t\t12345\n \t},\n \n+\t/*\n+\t * Let's fail at the tls negotiation various ways\n+\t */\n+\n+\t{\n+\t\t\u0022h1:badcert_hostname\u0022,\n+\t\t\u0022badcert_hostname\u0022, 5 * LWS_US_PER_SEC, LWSSSCS_TIMEOUT,\n+\t\t(1 \u003c\u003c LWSSSCS_QOS_NACK_REMOTE) |\n+\t\t(1 \u003c\u003c LWSSSCS_ALL_RETRIES_FAILED)\n+\t},\n+\t{\n+\t\t\u0022h1:badcert_expired\u0022,\n+\t\t\u0022badcert_expired\u0022, 5 * LWS_US_PER_SEC, LWSSSCS_TIMEOUT,\n+\t\t(1 \u003c\u003c LWSSSCS_QOS_NACK_REMOTE) |\n+\t\t(1 \u003c\u003c LWSSSCS_ALL_RETRIES_FAILED)\n+\t},\n+\t{\n+\t\t\u0022h1:badcert_selfsigned\u0022,\n+\t\t\u0022badcert_selfsigned\u0022, 5 * LWS_US_PER_SEC, LWSSSCS_TIMEOUT,\n+\t\t(1 \u003c\u003c LWSSSCS_QOS_NACK_REMOTE) |\n+\t\t(1 \u003c\u003c LWSSSCS_ALL_RETRIES_FAILED)\n+\t},\n+\n };\n \n typedef struct myss {\n@@ -675,6 +750,29 @@ static lws_state_notify_link_t * const app_notifier_list[] \u003d {\n \t\u0026nl, NULL\n };\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+static int\n+my_metric_report(lws_metric_pub_t *mp)\n+{\n+\tlws_metric_bucket_t *sub \u003d mp-\u003eu.hist.head;\n+\tchar buf[192];\n+\n+\tdo {\n+\t\tif (lws_metrics_format(mp, \u0026sub, buf, sizeof(buf)))\n+\t\t\tlwsl_user(\u0022%s: %s\u005cn\u0022, __func__, buf);\n+\t} while ((mp-\u003eflags \u0026 LWSMTFL_REPORT_HIST) \u0026\u0026 sub);\n+\n+\t/* 0 \u003d leave metric to accumulate, 1 \u003d reset the metric */\n+\n+\treturn 1;\n+}\n+\n+static const lws_system_ops_t system_ops \u003d {\n+\t.metric_report \u003d my_metric_report,\n+};\n+\n+#endif\n+\n static void\n sigint_handler(int sig)\n {\n@@ -740,6 +838,13 @@ main(int argc, const char **argv)\n \tnl.notify_cb \u003d app_system_state_nf;\n \tinfo.register_notifier_list \u003d app_notifier_list;\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\tinfo.system_ops \u003d \u0026system_ops;\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\tinfo.metrics_prefix \u003d \u0022ssmex\u0022;\n+#endif\n+#endif\n+\n \t/* create the context */\n \n \tcontext \u003d lws_create_context(\u0026info);\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c\nindex cd3f73a..7000629 100644\n--- a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c\n+++ b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c\n@@ -125,7 +125,7 @@ static const char * const default_ss_policy \u003d\n #if defined(VIA_LOCALHOST_SOCKS)\n \t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\u0022\u005c\u0022policy/minimal-proxy-socks.json\u005c\u0022,\u0022\n #else\n-\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\u0022\u005c\u0022policy/minimal-proxy-2.json\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\u0022\u005c\u0022policy/minimal-proxy-v4.2.json\u005c\u0022,\u0022\n #endif\n \t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\u0022true,\u0022\n \t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022:\u0022\t\u0022true,\u0022\n@@ -363,6 +363,30 @@ static lws_state_notify_link_t * const app_notifier_list[] \u003d {\n \t\u0026nl, NULL\n };\n \n+#if defined(LWS_WITH_SYS_METRICS)\n+\n+static int\n+my_metric_report(lws_metric_pub_t *mp)\n+{\n+\tlws_metric_bucket_t *sub \u003d mp-\u003eu.hist.head;\n+\tchar buf[192];\n+\n+\tdo {\n+\t\tif (lws_metrics_format(mp, \u0026sub, buf, sizeof(buf)))\n+\t\t\tlwsl_user(\u0022%s: %s\u005cn\u0022, __func__, buf);\n+\t} while ((mp-\u003eflags \u0026 LWSMTFL_REPORT_HIST) \u0026\u0026 sub);\n+\n+\t/* 0 \u003d leave metric to accumulate, 1 \u003d reset the metric */\n+\n+\treturn 1;\n+}\n+\n+static const lws_system_ops_t system_ops \u003d {\n+\t.metric_report \u003d my_metric_report,\n+};\n+\n+#endif\n+\n static void\n sigint_handler(int sig)\n {\n@@ -425,10 +449,6 @@ int main(int argc, const char **argv)\n \t\t LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW |\n \t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n #endif\n-#if defined(LWS_WITH_DETAILED_LATENCY)\n-\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n-\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n-#endif\n \n \t/* integrate us with lws system state management when context created */\n \n@@ -436,6 +456,12 @@ int main(int argc, const char **argv)\n \tnl.notify_cb \u003d app_system_state_nf;\n \tinfo.register_notifier_list \u003d app_notifier_list;\n \n+\n+#if defined(LWS_WITH_SYS_METRICS)\n+\tinfo.system_ops \u003d \u0026system_ops;\n+\tinfo.metrics_prefix \u003d \u0022ssmex\u0022;\n+#endif\n+\n \t/* create the context */\n \n \tcontext \u003d lws_create_context(\u0026info);\ndiff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt\nindex 2f252f9..15acd5b 100644\n--- a/plugins/CMakeLists.txt\n+++ b/plugins/CMakeLists.txt\n@@ -147,13 +147,12 @@ if (LWS_ROLE_WS)\n \t\t\tendif()\n \t\tendif()\n \n-\tif (LWS_WITH_SERVER_STATUS)\n-\t\t\tcreate_plugin(protocol_lws_server_status \u0022\u0022\n-\t\t\t\t \u0022protocol_lws_server_status.c\u0022 \u0022\u0022 \u0022\u0022)\n+\tif (LWS_WITH_SYS_METRICS)\n+\t\t\tcreate_plugin(protocol_lws_openmetrics_export \u0022\u0022\n+\t\t\t\t \u0022protocol_lws_openmetrics_export.c\u0022 \u0022\u0022 \u0022\u0022)\n \t\t\tif (NOT LWS_WITH_PLUGINS_BUILTIN)\n-\t\t\t\ttarget_compile_definitions(protocol_lws_server_status PRIVATE LWS_BUILDING_SHARED)\n+\t\t\t\ttarget_compile_definitions(protocol_lws_openmetrics_export PRIVATE LWS_BUILDING_SHARED)\n \t\t\tendif()\n-\n \tendif()\n \n \tif (NOT LWS_WITHOUT_CLIENT)\n@@ -237,11 +236,6 @@ if (LWS_WITH_PLUGINS AND NOT LWS_WITH_PLUGINS_BUILTIN)\n \tendif()\n \n \n-if (LWS_WITH_SERVER_STATUS)\n-\tinstall(FILES server-status.html;server-status.js;server-status.css;lwsws-logo.png\n-\t\tDESTINATION share/libwebsockets-test-server/server-status\n-\t\t\tCOMPONENT examples)\n-endif()\n endif()\n \n export_to_parent_intermediate()\ndiff --git a/plugins/protocol_lws_openmetrics_export.c b/plugins/protocol_lws_openmetrics_export.c\nnew file mode 100644\nindex 0000000..ba79fef\n--- /dev/null\n+++ b/plugins/protocol_lws_openmetrics_export.c\n@@ -0,0 +1,1200 @@\n+/*\n+ * libwebsockets-test-server - libwebsockets test implementation\n+ *\n+ * Written in 2010-2021 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ *\n+ * The person who associated a work with this deed has dedicated\n+ * the work to the public domain by waiving all of his or her rights\n+ * to the work worldwide under copyright law, including all related\n+ * and neighboring rights, to the extent allowed by law. You can copy,\n+ * modify, distribute and perform the work, even for commercial purposes,\n+ * all without asking permission.\n+ *\n+ * The test apps are intended to be adapted for use in your code, which\n+ * may be proprietary. So unlike the library itself, they are licensed\n+ * Public Domain.\n+ *\n+ * Scrapeable, proxiable OpenMetrics metrics (compatible with Prometheus)\n+ *\n+ * https://tools.ietf.org/html/draft-richih-opsawg-openmetrics-00\n+ *\n+ * This plugin provides four protocols related to openmetrics handling:\n+ *\n+ * 1) \u0022lws-openmetrics\u0022 direct http listener so scraper can directly get metrics\n+ *\n+ * 2) \u0022lws-openmetrics-prox-agg\u0022 metrics proxy server that scraper can connect\n+ * to locally to proxy through to connected remote clients at 3)\n+ *\n+ * 3) \u0022lws-openmetrics-prox-server\u0022 metrics proxy server that remote clients can\n+ * connect to, providing a path where scrapers at 2) can get metrics from\n+ * clients connected us\n+ *\n+ * 4) \u0022lws-openmetrics-prox-client\u0022 nailed-up metrics proxy client that tries to\n+ * keep up a connection to the server at 3), allowing to scraper to reach\n+ * clients that have no reachable way to serve.\n+ *\n+ * These are provided like this to maximize flexibility in being able to add\n+ * openmetrics serving, proxying, or client-\u003eproxy to existing lws code.\n+ *\n+ * Openmetrics supports a \u0022metric\u0022 at the top of its report that describes the\n+ * source aka \u0022target metadata\u0022.\n+ *\n+ * Since we want to enable collection from devices that are not externally\n+ * reachable, we must provide a reachable server that the clients can attach to\n+ * and have their stats aggregated and then read by Prometheus or whatever.\n+ * Openmetrics says that it wants to present the aggregated stats in a flat\n+ * summary with only the aggregator's \u0022target metadata\u0022 and contributor targets\n+ * getting their data tagged with the source\n+ *\n+ * \u0022The above discussion is in the context of individual exposers. An\n+ * exposition from a general purpose monitoring system may contain\n+ * metrics from many individual targets, and thus may expose multiple\n+ * target info Metrics. The metrics may already have had target\n+ * metadata added to them as labels as part of ingestion. The metric\n+ * names MUST NOT be varied based on target metadata. For example it\n+ * would be incorrect for all metrics to end up being prefixed with\n+ * staging_ even if they all originated from targets in a staging\n+ * environment).\u0022\n+ */\n+\n+#if !defined (LWS_PLUGIN_STATIC)\n+#if !defined(LWS_DLL)\n+#define LWS_DLL\n+#endif\n+#if !defined(LWS_INTERNAL)\n+#define LWS_INTERNAL\n+#endif\n+#include \u003clibwebsockets.h\u003e\n+#endif\n+#include \u003cstring.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003cfcntl.h\u003e\n+#if !defined(WIN32)\n+#include \u003cunistd.h\u003e\n+#endif\n+#include \u003cassert.h\u003e\n+\n+struct vhd {\n+\tstruct lws_context\t*cx;\n+\tstruct lws_vhost\t*vhost;\n+\n+\tchar\t\t\tws_server_uri[128];\n+\tchar\t\t\tmetrics_proxy_path[128];\n+\tchar\t\t\tba_secret[128];\n+\n+\tconst char\t\t*proxy_side_bind_name;\n+\t/**\u003c name used to bind the two halves of the proxy together, must be\n+\t * the same name given in a pvo for both \u0022lws-openmetrics-prox-agg\u0022\n+\t * (the side local to the scraper) and \u0022lws-openmetrics-prox-server\u0022\n+\t * (the side the clients connect to)\n+\t */\n+\n+\tchar\t\t\tsanity[8];\n+\n+\tlws_dll2_owner_t\tclients;\n+\n+\tlws_sorted_usec_list_t\tsul;\t /* schedule connection retry */\n+\n+\tstruct vhd\t\t*bind_partner_vhd;\n+\n+\tstruct lws\t\t*wsi;\t /* related wsi if any */\n+\tuint16_t\t\tretry_count; /* count of consequetive retries */\n+};\n+\n+struct pss {\n+\tlws_dll2_t\t\tlist;\n+\tchar\t\t\tproxy_path[64];\n+\tstruct lwsac\t\t*ac;\t/* the translated metrics, one ac per line */\n+\tstruct lwsac\t\t*walk;\t/* iterator for ac when writing */\n+\tsize_t\t\t\ttot;\t/* content-length computation */\n+\tstruct lws\t\t*wsi;\n+\n+\tuint8_t\t\t\tgreet:1; /* set if client needs to send proxy path */\n+\tuint8_t\t\t\ttrigger:1; /* we want to ask the client to dump */\n+};\n+\n+#if defined(LWS_WITH_CLIENT)\n+static const uint32_t backoff_ms[] \u003d { 1000, 2000, 3000, 4000, 5000 };\n+\n+static const lws_retry_bo_t retry \u003d {\n+\t.retry_ms_table\t\t\t\u003d backoff_ms,\n+\t.retry_ms_table_count\t\t\u003d LWS_ARRAY_SIZE(backoff_ms),\n+\t.conceal_count\t\t\t\u003d LWS_ARRAY_SIZE(backoff_ms),\n+\n+\t.secs_since_valid_ping\t\t\u003d 400, /* force PINGs after secs idle */\n+\t.secs_since_valid_hangup\t\u003d 400, /* hangup after secs idle */\n+\n+\t.jitter_percent\t\t\t\u003d 0,\n+};\n+\n+static void\n+omc_connect_client(lws_sorted_usec_list_t *sul)\n+{\n+\tstruct vhd *vhd \u003d lws_container_of(sul, struct vhd, sul);\n+\tstruct lws_client_connect_info i;\n+\tconst char *prot;\n+\tchar url[128];\n+\n+\tmemset(\u0026i, 0, sizeof(i));\n+\n+\tlwsl_notice(\u0022%s: %s %s %s\u005cn\u0022, __func__, vhd-\u003ews_server_uri, vhd-\u003emetrics_proxy_path, vhd-\u003eba_secret);\n+\n+\tlws_strncpy(url, vhd-\u003ews_server_uri, sizeof(url));\n+\n+\tif (lws_parse_uri(url, \u0026prot, \u0026i.address, \u0026i.port, \u0026i.path)) {\n+\t\tlwsl_err(\u0022%s: unable to parse uri %s\u005cn\u0022, __func__,\n+\t\t\t vhd-\u003ews_server_uri);\n+\t\treturn;\n+\t}\n+\n+\ti.context\t\t\u003d vhd-\u003ecx;\n+\ti.origin\t\t\u003d i.address;\n+\ti.host\t\t\t\u003d i.address;\n+\ti.ssl_connection\t\u003d LCCSCF_USE_SSL;\n+\ti.protocol\t\t\u003d \u0022lws-openmetrics-prox-server\u0022; /* public subprot */\n+\ti.local_protocol_name\t\u003d \u0022lws-openmetrics-prox-client\u0022;\n+\ti.pwsi\t\t\t\u003d \u0026vhd-\u003ewsi;\n+\ti.retry_and_idle_policy \u003d \u0026retry;\n+\ti.userdata\t\t\u003d vhd;\n+\ti.vhost\t\t\t\u003d vhd-\u003evhost;\n+\n+\tlwsl_notice(\u0022%s: %s %u %s\u005cn\u0022, __func__, i.address, i.port, i.path);\n+\n+\tif (lws_client_connect_via_info(\u0026i))\n+\t\treturn;\n+\n+\t/*\n+\t * Failed... schedule a retry... we can't use the _retry_wsi()\n+\t * convenience wrapper api here because no valid wsi at this\n+\t * point.\n+\t */\n+\tif (!lws_retry_sul_schedule(vhd-\u003ecx, 0, sul, \u0026retry,\n+\t\t\t\t omc_connect_client, \u0026vhd-\u003eretry_count))\n+\t\treturn;\n+\n+\tvhd-\u003eretry_count \u003d 0;\n+\tlws_retry_sul_schedule(vhd-\u003ecx, 0, sul, \u0026retry,\n+\t\t\t omc_connect_client, \u0026vhd-\u003eretry_count);\n+}\n+#endif\n+\n+static void\n+openmetrics_san(char *nm, size_t nl)\n+{\n+\tsize_t m;\n+\n+\t/* Openmetrics has a very restricted token charset */\n+\n+\tfor (m \u003d 0; m \u003c nl; m++)\n+\t\tif ((nm[m] \u003c 'A' || nm[m] \u003e 'Z') \u0026\u0026\n+\t\t (nm[m] \u003c 'a' || nm[m] \u003e 'z') \u0026\u0026\n+\t\t (nm[m] \u003c '0' || nm[m] \u003e '9') \u0026\u0026\n+\t\t nm[m] !\u003d '_')\n+\t\t\tnm[m] \u003d '_';\n+}\n+\n+static int\n+lws_metrics_om_format_agg(lws_metric_pub_t *pub, const char *nm, lws_usec_t now,\n+\t\t\t int gng, char *buf, size_t len)\n+{\n+\tconst char *_gng \u003d gng ? \u0022_nogo\u0022 : \u0022_go\u0022;\n+\tchar *end \u003d buf + len - 1, *obuf \u003d buf;\n+\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_ONLY_GO)\n+\t\t_gng \u003d \u0022\u0022;\n+\n+\tif (!(pub-\u003eflags \u0026 LWSMTFL_REPORT_MEAN)) {\n+\t\t/* only the sum is meaningful */\n+\t\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_DUTY_WALLCLOCK_US) {\n+\t\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n+\t\t\t\t\u0022%s_count %u\u005cn\u0022\n+\t\t\t\t\u0022%s_us_sum %llu\u005cn\u0022\n+\t\t\t\t\u0022%s_created %lu.%06u\u005cn\u0022,\n+\t\t\t\tnm, (unsigned int)pub-\u003eu.agg.count[gng],\n+\t\t\t\tnm, (unsigned long long)pub-\u003eu.agg.sum[gng],\n+\t\t\t\tnm, (unsigned long)(pub-\u003eus_first / 1000000),\n+\t\t\t\t (unsigned int)(pub-\u003eus_first % 1000000));\n+\n+\t\t\treturn lws_ptr_diff(buf, obuf);\n+\t\t}\n+\n+\t\t/* it's a monotonic ordinal, like total tx */\n+\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n+\t\t\t\t \u0022%s%s_count %u\u005cn\u0022\n+\t\t\t\t \u0022%s%s_sum %llu\u005cn\u0022,\n+\t\t\t\t nm, _gng,\n+\t\t\t\t (unsigned int)pub-\u003eu.agg.count[gng],\n+\t\t\t\t nm, _gng,\n+\t\t\t\t (unsigned long long)pub-\u003eu.agg.sum[gng]);\n+\n+\t} else\n+\t\tbuf +\u003d lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),\n+\t\t\t\t \u0022%s%s_count %u\u005cn\u0022\n+\t\t\t\t \u0022%s%s_mean %llu\u005cn\u0022,\n+\t\t\t\t nm, _gng,\n+\t\t\t\t (unsigned int)pub-\u003eu.agg.count[gng],\n+\t\t\t\t nm, _gng, (unsigned long long)\n+\t\t\t\t (pub-\u003eu.agg.count[gng] ?\n+\t\t\t\t\t\tpub-\u003eu.agg.sum[gng] /\n+\t\t\t\t\t\tpub-\u003eu.agg.count[gng] : 0));\n+\n+\treturn lws_ptr_diff(buf, obuf);\n+}\n+\n+static int\n+lws_metrics_om_ac_stash(struct pss *pss, const char *buf, size_t len)\n+{\n+\tchar *q;\n+\n+\tq \u003d lwsac_use(\u0026pss-\u003eac, LWS_PRE + len + 2, LWS_PRE + len + 2);\n+\tif (!q) {\n+\t\tlwsac_free(\u0026pss-\u003eac);\n+\n+\t\treturn -1;\n+\t}\n+\tq[LWS_PRE] \u003d (char)((len \u003e\u003e 8) \u0026 0xff);\n+\tq[LWS_PRE + 1] \u003d (char)(len \u0026 0xff);\n+\tmemcpy(q + LWS_PRE + 2, buf, len);\n+\tpss-\u003etot +\u003d len;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * We have to do the ac listing at this level, because there can be too large\n+ * a number to metrics tags to iterate that can fit in a reasonable buffer.\n+ */\n+\n+static int\n+lws_metrics_om_format(struct pss *pss, lws_metric_pub_t *pub, const char *nm)\n+{\n+\tchar buf[1200], *p \u003d buf, *end \u003d buf + sizeof(buf) - 1, tmp[512];\n+\tlws_usec_t t \u003d lws_now_usecs();\n+\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_HIST) {\n+\t\tlws_metric_bucket_t *buck \u003d pub-\u003eu.hist.head;\n+\n+\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p),\n+\t\t\t\t \u0022%s_count %llu\u005cn\u0022,\n+\t\t\t\t nm, (unsigned long long)\n+\t\t\t\t pub-\u003eu.hist.total_count);\n+\n+\t\twhile (buck) {\n+\t\t\tlws_strncpy(tmp, lws_metric_bucket_name(buck),\n+\t\t\t\t sizeof(tmp));\n+\n+\t\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p),\n+\t\t\t\t\t \u0022%s{%s} %llu\u005cn\u0022, nm, tmp,\n+\t\t\t\t\t (unsigned long long)buck-\u003ecount);\n+\n+\t\t\tlws_metrics_om_ac_stash(pss, buf,\n+\t\t\t\t\t\tlws_ptr_diff_size_t(p, buf));\n+\t\t\tp \u003d buf;\n+\n+\t\t\tbuck \u003d buck-\u003enext;\n+\t\t}\n+\n+\t\tgoto happy;\n+\t}\n+\n+\tif (!pub-\u003eu.agg.count[METRES_GO] \u0026\u0026 !pub-\u003eu.agg.count[METRES_NOGO])\n+\t\treturn 0;\n+\n+\tif (pub-\u003eu.agg.count[METRES_GO])\n+\t\tp +\u003d lws_metrics_om_format_agg(pub, nm, t, METRES_GO, p,\n+\t\t\t\t\t lws_ptr_diff_size_t(end, p));\n+\n+\tif (!(pub-\u003eflags \u0026 LWSMTFL_REPORT_ONLY_GO) \u0026\u0026\n+\t pub-\u003eu.agg.count[METRES_NOGO])\n+\t\tp +\u003d lws_metrics_om_format_agg(pub, nm, t, METRES_NOGO, p,\n+\t\t\t\t\t lws_ptr_diff_size_t(end, p));\n+\n+\tif (pub-\u003eflags \u0026 LWSMTFL_REPORT_MEAN)\n+\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p),\n+\t\t\t\t \u0022%s_min %llu\u005cn\u0022\n+\t\t\t\t \u0022%s_max %llu\u005cn\u0022,\n+\t\t\t\t nm, (unsigned long long)pub-\u003eu.agg.min,\n+\t\t\t\t nm, (unsigned long long)pub-\u003eu.agg.max);\n+\n+happy:\n+\treturn lws_metrics_om_ac_stash(pss, buf, lws_ptr_diff_size_t(p, buf));\n+}\n+\n+static int\n+append_om_metric(lws_metric_pub_t *pub, void *user)\n+{\n+\tstruct pss *pss \u003d (struct pss *)user;\n+\tchar nm[64];\n+\tsize_t nl;\n+\n+\t/*\n+\t * Convert lws_metrics to openmetrics metrics data, stashing into an\n+\t * lwsac without backfill. Since it's not backfilling, use areas are in\n+\t * linear sequence simplifying walking them. Limiting the lwsac alloc\n+\t * to less than a typical mtu means we can write one per write\n+\t * efficiently\n+\t */\n+\n+\tlws_strncpy(nm, pub-\u003ename, sizeof(nm));\n+\tnl \u003d strlen(nm);\n+\n+\topenmetrics_san(nm, nl);\n+\n+\treturn lws_metrics_om_format(pss, pub, nm);\n+}\n+\n+#if defined(__linux__)\n+static int\n+grabfile(const char *fi, char *buf, size_t len)\n+{\n+\tint n, fd \u003d lws_open(fi, LWS_O_RDONLY);\n+\n+\tbuf[0] \u003d '\u005c0';\n+\tif (fd \u003c 0)\n+\t\treturn -1;\n+\n+\tn \u003d (int)read(fd, buf, len - 1);\n+\tclose(fd);\n+\tif (n \u003c 0) {\n+\t\tbuf[0] \u003d '\u005c0';\n+\t\treturn -1;\n+\t}\n+\n+\tbuf[n] \u003d '\u005c0';\n+\tif (n \u003e 0 \u0026\u0026 buf[n - 1] \u003d\u003d '\u005cn')\n+\t\tbuf[--n] \u003d '\u005c0';\n+\n+\treturn n;\n+}\n+#endif\n+\n+/*\n+ * Let's pregenerate the output into an lwsac all at once and\n+ * then spool it back to the peer afterwards\n+ *\n+ * - there's not going to be that much of it (a few kB)\n+ * - we then know the content-length for the headers\n+ * - it's stretchy to arbitrary numbers of metrics\n+ * - lwsac block list provides the per-metric structure to\n+ * hold the data in a way we can walk to write it simply\n+ */\n+\n+int\n+ome_prepare(struct lws_context *ctx, struct pss *pss)\n+{\n+\tchar buf[1224], *start \u003d buf + LWS_PRE, *p \u003d start,\n+\t *end \u003d buf + sizeof(buf) - 1;\n+\tchar hn[64];\n+\n+\tpss-\u003etot \u003d 0;\n+\n+\t/*\n+\t * Target metadata\n+\t */\n+\n+\thn[0] \u003d '\u005c0';\n+\tgethostname(hn, sizeof(hn) - 1);\n+\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p),\n+\t\t\t \u0022# TYPE target info\u005cn\u0022\n+\t\t\t \u0022# HELP target Target metadata\u005cn\u0022\n+\t\t\t \u0022target_info{hostname\u003d\u005c\u0022%s\u005c\u0022\u0022, hn);\n+\n+#if defined(__linux__)\n+\tif (grabfile(\u0022/proc/self/cmdline\u0022, hn, sizeof(hn)))\n+\t\tp +\u003d lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),\n+\t\t\t\t \u0022,cmdline\u003d\u005c\u0022%s\u005c\u0022\u0022, hn);\n+#endif\n+\n+\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022} 1\u005cn\u0022);\n+\n+\tif (lws_metrics_om_ac_stash(pss, (const char *)buf + LWS_PRE,\n+\t\t\t\t lws_ptr_diff_size_t(p, buf + LWS_PRE)))\n+\t\treturn 1;\n+\n+\t/* lws version */\n+\n+\tp \u003d start;\n+\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p),\n+\t\t\t \u0022# TYPE lws_info info\u005cn\u0022\n+\t\t\t \u0022# HELP lws_info Version of lws producing this\u005cn\u0022\n+\t\t\t \u0022lws_info{version\u003d\u005c\u0022%s\u005c\u0022} 1\u005cn\u0022, LWS_BUILD_HASH);\n+\tif (lws_metrics_om_ac_stash(pss, (const char *)buf + LWS_PRE,\n+\t\t\t\t lws_ptr_diff_size_t(p, buf + LWS_PRE)))\n+\t\treturn 1;\n+\n+\t/* system scalars */\n+\n+#if defined(__linux__)\n+\tif (grabfile(\u0022/proc/loadavg\u0022, hn, sizeof(hn))) {\n+\t\tchar *sp \u003d strchr(hn, ' ');\n+\t\tif (sp) {\n+\t\t\tp \u003d start;\n+\t\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p),\n+\t\t\t\t\t \u0022load_1m %.*s\u005cn\u0022,\n+\t\t\t\t\t lws_ptr_diff(sp, hn), hn);\n+\t\t\tif (lws_metrics_om_ac_stash(pss,\n+\t\t\t\t\t\t (char *)buf + LWS_PRE,\n+\t\t\t\t\t\t lws_ptr_diff_size_t(p,\n+\t\t\t\t\t\t\t\tstart)))\n+\t\t\t\treturn 1;\n+\t\t}\n+\t}\n+#endif\n+\n+\tif (lws_metrics_foreach(ctx, pss, append_om_metric))\n+\t\treturn 1;\n+\n+\tp \u003d start;\n+\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p),\n+\t\t\t \u0022# EOF\u005cn\u0022);\n+\tif (lws_metrics_om_ac_stash(pss, (char *)buf + LWS_PRE,\n+\t\t\t\t lws_ptr_diff_size_t(p, buf + LWS_PRE)))\n+\t\treturn 1;\n+\n+\tpss-\u003ewalk \u003d pss-\u003eac;\n+\n+\treturn 0;\n+}\n+\n+#if defined(LWS_WITH_SERVER)\n+\n+/* 1) direct http export for scraper */\n+\n+static int\n+callback_lws_openmetrics_export(struct lws *wsi,\n+\t\t\t\tenum lws_callback_reasons reason,\n+\t\t\t\tvoid *user, void *in, size_t len)\n+{\n+\tunsigned char buf[1224], *start \u003d buf + LWS_PRE, *p \u003d start,\n+\t\t *end \u003d buf + sizeof(buf) - 1, *ip;\n+\tstruct lws_context *cx \u003d lws_get_context(wsi);\n+\tstruct pss *pss \u003d (struct pss *)user;\n+\tunsigned int m, wm;\n+\n+\tswitch (reason) {\n+\tcase LWS_CALLBACK_HTTP:\n+\n+\t\tome_prepare(cx, pss);\n+\n+\t\tp \u003d start;\n+\t\tif (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,\n+\t\t\t\t\t\t\u0022application/openmetrics-text; \u0022\n+\t\t\t\t\t\t\u0022version\u003d1.0.0; charset\u003dutf-8\u0022,\n+\t\t\t\t\t\tpss-\u003etot, \u0026p, end) ||\n+\t\t lws_finalize_write_http_header(wsi, start, \u0026p, end))\n+\t\t\treturn 1;\n+\n+\t\tlws_callback_on_writable(wsi);\n+\n+\t\treturn 0;\n+\n+\tcase LWS_CALLBACK_CLOSED_HTTP:\n+\t\tlwsac_free(\u0026pss-\u003eac);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_HTTP_WRITEABLE:\n+\t\tif (!pss-\u003ewalk)\n+\t\t\treturn 0;\n+\n+\t\tdo {\n+\t\t\tip \u003d (uint8_t *)pss-\u003ewalk +\n+\t\t\t\tlwsac_sizeof(pss-\u003ewalk \u003d\u003d pss-\u003eac) + LWS_PRE;\n+\t\t\tm \u003d (unsigned int)((ip[0] \u003c\u003c 8) | ip[1]);\n+\n+\t\t\t/* coverity */\n+\t\t\tif (m \u003e lwsac_get_tail_pos(pss-\u003ewalk) -\n+\t\t\t\tlwsac_sizeof(pss-\u003ewalk \u003d\u003d pss-\u003eac))\n+\t\t\t\treturn -1;\n+\n+\t\t\tif (lws_ptr_diff_size_t(end, p) \u003c m)\n+\t\t\t\tbreak;\n+\n+\t\t\tmemcpy(p, ip + 2, m);\n+\t\t\tp +\u003d m;\n+\n+\t\t\tpss-\u003ewalk \u003d lwsac_get_next(pss-\u003ewalk);\n+\t\t} while (pss-\u003ewalk);\n+\n+\t\tif (!lws_ptr_diff_size_t(p, start)) {\n+\t\t\tlwsl_err(\u0022%s: stuck\u005cn\u0022, __func__);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\twm \u003d pss-\u003ewalk ? LWS_WRITE_HTTP : LWS_WRITE_HTTP_FINAL;\n+\n+\t\tif (lws_write(wsi, start, lws_ptr_diff_size_t(p, start),\n+\t\t\t (enum lws_write_protocol)wm) \u003c 0)\n+\t\t\treturn 1;\n+\n+\t\tif (!pss-\u003ewalk) {\n+\t\t\t if (lws_http_transaction_completed(wsi))\n+\t\t\t\treturn -1;\n+\t\t} else\n+\t\t\tlws_callback_on_writable(wsi);\n+\n+\t\treturn 0;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+}\n+\n+static struct pss *\n+omc_lws_om_get_other_side_pss_client(struct vhd *vhd, struct pss *pss)\n+{\n+\t/*\n+\t * Search through our partner's clients list looking for one with the\n+\t * same proxy path\n+\t */\n+\tlws_start_foreach_dll(struct lws_dll2 *, d,\n+\t\t\tvhd-\u003ebind_partner_vhd-\u003eclients.head) {\n+\t\tstruct pss *apss \u003d lws_container_of(d, struct pss, list);\n+\n+\t\tif (!strcmp(pss-\u003eproxy_path, apss-\u003eproxy_path))\n+\t\t\treturn apss;\n+\n+\t} lws_end_foreach_dll(d);\n+\n+\treturn NULL;\n+}\n+\n+/* 2) \u0022lws-openmetrics-prox-agg\u0022: http server export via proxy to connected clients */\n+\n+static int\n+callback_lws_openmetrics_prox_agg(struct lws *wsi,\n+\t\t\t\t enum lws_callback_reasons reason,\n+\t\t\t\t void *user, void *in, size_t len)\n+{\n+\tunsigned char buf[1224], *start \u003d buf + LWS_PRE, *p \u003d start,\n+\t\t *end \u003d buf + sizeof(buf) - 1, *ip;\n+\tstruct vhd *vhd \u003d (struct vhd *)lws_protocol_vh_priv_get(\n+\t\t\t\tlws_get_vhost(wsi), lws_get_protocol(wsi));\n+\tstruct lws_context *cx \u003d lws_get_context(wsi);\n+\tstruct pss *pss \u003d (struct pss *)user, *partner_pss;\n+\tunsigned int m, wm;\n+\n+\tswitch (reason) {\n+\n+\tcase LWS_CALLBACK_PROTOCOL_INIT:\n+\t\tlwsl_notice(\u0022%s: PROTOCOL_INIT on %s\u005cn\u0022, __func__, lws_vh_tag(lws_get_vhost(wsi)));\n+\t\t/*\n+\t\t * We get told what to do when we are bound to the vhost\n+\t\t */\n+\t\tvhd \u003d lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),\n+\t\t\t\tlws_get_protocol(wsi), sizeof(struct vhd));\n+\t\tif (!vhd) {\n+\t\t\tlwsl_err(\u0022%s: vhd alloc failed\u005cn\u0022, __func__);\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\tvhd-\u003ecx \u003d cx;\n+\n+\t\t/*\n+\t\t * Try to bind to the counterpart server in the proxy, binding\n+\t\t * to the right one by having a common bind name set in a pvo.\n+\t\t * We don't know who will get instantiated last, so both parts\n+\t\t * try to bind if not already bound\n+\t\t */\n+\n+\t\tif (!lws_pvo_get_str(in, \u0022proxy-side-bind-name\u0022,\n+\t\t\t\t \u0026vhd-\u003eproxy_side_bind_name)) {\n+\t\t\t/*\n+\t\t\t * Attempt to find the vhd that belongs to a vhost\n+\t\t\t * that has instantiated protocol\n+\t\t\t * \u0022lws-openmetrics-prox-server\u0022, and has set pvo\n+\t\t\t * \u0022proxy-side-bind-name\u0022 on it to whatever our\n+\t\t\t * vhd-\u003eproxy_side_bind_name was also set to.\n+\t\t\t *\n+\t\t\t * If found, inform the two sides of the same proxy\n+\t\t\t * what their partner vhd is\n+\t\t\t */\n+\t\t\tlws_strncpy(vhd-\u003esanity, \u0022isagg\u0022, sizeof(vhd-\u003esanity));\n+\t\t\tvhd-\u003ebind_partner_vhd \u003d lws_vhd_find_by_pvo(cx,\n+\t\t\t\t\t\t\u0022lws-openmetrics-prox-server\u0022,\n+\t\t\t\t\t\t\u0022proxy-side-bind-name\u0022,\n+\t\t\t\t\t\tvhd-\u003eproxy_side_bind_name);\n+\t\t\tif (vhd-\u003ebind_partner_vhd) {\n+\t\t\t\tassert(!strcmp(vhd-\u003ebind_partner_vhd-\u003esanity, \u0022isws\u0022));\n+\t\t\t\tlwsl_notice(\u0022%s: proxy binding OK\u005cn\u0022, __func__);\n+\t\t\t\tvhd-\u003ebind_partner_vhd-\u003ebind_partner_vhd \u003d vhd;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tlwsl_warn(\u0022%s: proxy-side-bind-name required\u005cn\u0022, __func__);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_PROTOCOL_DESTROY:\n+\t\tif (vhd)\n+\t\t\tlws_sul_cancel(\u0026vhd-\u003esul);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_HTTP:\n+\n+\t\t/*\n+\t\t * The scraper has connected to us, the local side of the proxy,\n+\t\t * we need to match what it wants to\n+\t\t */\n+\n+\t\tif (!vhd-\u003ebind_partner_vhd)\n+\t\t\treturn 0;\n+\n+\t\tlws_strnncpy(pss-\u003eproxy_path, (const char *)in, len,\n+\t\t\t sizeof(pss-\u003eproxy_path));\n+\n+\t\tif (pss-\u003elist.owner) {\n+\t\t\tlwsl_warn(\u0022%s: double HTTP?\u005cn\u0022, __func__);\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\tpss-\u003ewsi \u003d wsi;\n+\n+\t\tlws_start_foreach_dll(struct lws_dll2 *, d,\n+\t\t\t\t vhd-\u003ebind_partner_vhd-\u003eclients.head) {\n+\t\t\tstruct pss *apss \u003d lws_container_of(d, struct pss, list);\n+\n+\t\t\tif (!strcmp((const char *)in, apss-\u003eproxy_path)) {\n+\t\t\t\tapss-\u003etrigger \u003d 1;\n+\t\t\t\tlws_callback_on_writable(apss-\u003ewsi);\n+\n+\t\t\t\t/* let's add him on the http server vhd list */\n+\n+\t\t\t\tlws_dll2_add_tail(\u0026pss-\u003elist, \u0026vhd-\u003eclients);\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\n+\t\t} lws_end_foreach_dll(d);\n+\n+\t\treturn 0;\n+\n+\tcase LWS_CALLBACK_CLOSED_HTTP:\n+\t\tlwsac_free(\u0026pss-\u003eac);\n+\t\tlws_dll2_remove(\u0026pss-\u003elist);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_HTTP_WRITEABLE:\n+\n+\t\tif (!pss-\u003ewalk)\n+\t\t\treturn 0;\n+\n+\t\t/* locate the wss side if it's still around */\n+\n+\t\tpartner_pss \u003d omc_lws_om_get_other_side_pss_client(vhd, pss);\n+\t\tif (!partner_pss)\n+\t\t\treturn -1;\n+\n+\t\tdo {\n+\t\t\tip \u003d (uint8_t *)pss-\u003ewalk +\n+\t\t\t\tlwsac_sizeof(pss-\u003ewalk \u003d\u003d partner_pss-\u003eac) + LWS_PRE;\n+\t\t\tm \u003d (unsigned int)((ip[0] \u003c\u003c 8) | ip[1]);\n+\n+\t\t\t/* coverity */\n+\t\t\tif (m \u003e lwsac_get_tail_pos(pss-\u003ewalk) -\n+\t\t\t\tlwsac_sizeof(pss-\u003ewalk \u003d\u003d partner_pss-\u003eac))\n+\t\t\t\treturn -1;\n+\n+\t\t\tif (lws_ptr_diff_size_t(end, p) \u003c m)\n+\t\t\t\tbreak;\n+\n+\t\t\tmemcpy(p, ip + 2, m);\n+\t\t\tp +\u003d m;\n+\n+\t\t\tpss-\u003ewalk \u003d lwsac_get_next(pss-\u003ewalk);\n+\t\t} while (pss-\u003ewalk);\n+\n+\t\tif (!lws_ptr_diff_size_t(p, start)) {\n+\t\t\tlwsl_err(\u0022%s: stuck\u005cn\u0022, __func__);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\twm \u003d pss-\u003ewalk ? LWS_WRITE_HTTP : LWS_WRITE_HTTP_FINAL;\n+\n+\t\tif (lws_write(wsi, start, lws_ptr_diff_size_t(p, start),\n+\t\t\t (enum lws_write_protocol)wm) \u003c 0)\n+\t\t\treturn 1;\n+\n+\t\tif (!pss-\u003ewalk) {\n+\t\t\tlwsl_info(\u0022%s: whole msg proxied to scraper\u005cn\u0022, __func__);\n+\t\t\tlws_dll2_remove(\u0026pss-\u003elist);\n+\t\t\tlwsac_free(\u0026partner_pss-\u003eac);\n+//\t\t\tif (lws_http_transaction_completed(wsi))\n+\t\t\treturn -1;\n+\t\t} else\n+\t\t\tlws_callback_on_writable(wsi);\n+\n+\t\treturn 0;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+}\n+\n+/* 3) \u0022lws-openmetrics-prox-server\u0022: ws server side of metrics proxy, for\n+ * ws clients to connect to */\n+\n+static int\n+callback_lws_openmetrics_prox_server(struct lws *wsi,\n+\t\t\t\t enum lws_callback_reasons reason,\n+\t\t\t\t void *user, void *in, size_t len)\n+{\n+\tunsigned char buf[1224], *start \u003d buf + LWS_PRE, *p \u003d start,\n+\t\t *end \u003d buf + sizeof(buf) - 1;\n+\tstruct vhd *vhd \u003d (struct vhd *)lws_protocol_vh_priv_get(\n+\t\t\t\tlws_get_vhost(wsi), lws_get_protocol(wsi));\n+\tstruct lws_context *cx \u003d lws_get_context(wsi);\n+\tstruct pss *pss \u003d (struct pss *)user, *partner_pss;\n+\n+\tswitch (reason) {\n+\n+\tcase LWS_CALLBACK_PROTOCOL_INIT:\n+\t\t/*\n+\t\t * We get told what to do when we are bound to the vhost\n+\t\t */\n+\n+\t\tlwsl_notice(\u0022%s: PROTOCOL_INIT on %s\u005cn\u0022, __func__, lws_vh_tag(lws_get_vhost(wsi)));\n+\n+\t\tvhd \u003d lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),\n+\t\t\t\tlws_get_protocol(wsi), sizeof(struct vhd));\n+\t\tif (!vhd) {\n+\t\t\tlwsl_err(\u0022%s: vhd alloc failed\u005cn\u0022, __func__);\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\tvhd-\u003ecx \u003d cx;\n+\n+\t\t/*\n+\t\t * Try to bind to the counterpart server in the proxy, binding\n+\t\t * to the right one by having a common bind name set in a pvo.\n+\t\t * We don't know who will get instantiated last, so both parts\n+\t\t * try to bind if not already bound\n+\t\t */\n+\n+\t\tif (!lws_pvo_get_str(in, \u0022proxy-side-bind-name\u0022,\n+\t\t\t\t \u0026vhd-\u003eproxy_side_bind_name)) {\n+\t\t\t/*\n+\t\t\t * Attempt to find the vhd that belongs to a vhost\n+\t\t\t * that has instantiated protocol\n+\t\t\t * \u0022lws-openmetrics-prox-server\u0022, and has set pvo\n+\t\t\t * \u0022proxy-side-bind-name\u0022 on it to whatever our\n+\t\t\t * vhd-\u003eproxy_side_bind_name was also set to.\n+\t\t\t *\n+\t\t\t * If found, inform the two sides of the same proxy\n+\t\t\t * what their partner vhd is\n+\t\t\t */\n+\t\t\tlws_strncpy(vhd-\u003esanity, \u0022isws\u0022, sizeof(vhd-\u003esanity));\n+\t\t\tvhd-\u003ebind_partner_vhd \u003d lws_vhd_find_by_pvo(cx,\n+\t\t\t\t\t\t\u0022lws-openmetrics-prox-agg\u0022,\n+\t\t\t\t\t\t\u0022proxy-side-bind-name\u0022,\n+\t\t\t\t\t\tvhd-\u003eproxy_side_bind_name);\n+\t\t\tif (vhd-\u003ebind_partner_vhd) {\n+\t\t\t\tassert(!strcmp(vhd-\u003ebind_partner_vhd-\u003esanity, \u0022isagg\u0022));\n+\t\t\t\tlwsl_notice(\u0022%s: proxy binding OK\u005cn\u0022, __func__);\n+\t\t\t\tvhd-\u003ebind_partner_vhd-\u003ebind_partner_vhd \u003d vhd;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tlwsl_warn(\u0022%s: proxy-side-bind-name required\u005cn\u0022, __func__);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_PROTOCOL_DESTROY:\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_ESTABLISHED:\n+\t\t/*\n+\t\t * a client has joined... we need to add his pss to our list\n+\t\t * of live, joined clients\n+\t\t */\n+\n+\t\t/* mark us as waiting for the reference name from the client */\n+\t\tpss-\u003egreet \u003d 1;\n+\t\tpss-\u003ewsi \u003d wsi;\n+\t\tlws_validity_confirmed(wsi);\n+\n+\t\treturn 0;\n+\n+\tcase LWS_CALLBACK_CLOSED:\n+\t\t/*\n+\t\t * a client has parted\n+\t\t */\n+\t\tlws_dll2_remove(\u0026pss-\u003elist);\n+\t\tlwsl_warn(\u0022%s: client %s left (%u)\u005cn\u0022, __func__,\n+\t\t\t\tpss-\u003eproxy_path,\n+\t\t\t\t(unsigned int)vhd-\u003eclients.count);\n+\t\tlwsac_free(\u0026pss-\u003eac);\n+\n+\t\t/* let's kill the scraper connection accordingly, if still up */\n+\t\tpartner_pss \u003d omc_lws_om_get_other_side_pss_client(vhd, pss);\n+\t\tif (partner_pss)\n+\t\t\tlws_wsi_close(partner_pss-\u003ewsi, LWS_TO_KILL_ASYNC);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_RECEIVE:\n+\t\tif (pss-\u003egreet) {\n+\t\t\tpss-\u003egreet \u003d 0;\n+\t\t\tlws_strnncpy(pss-\u003eproxy_path, (const char *)in, len,\n+\t\t\t\t sizeof(pss-\u003eproxy_path));\n+\n+\t\t\tlws_validity_confirmed(wsi);\n+\t\t\tlwsl_notice(\u0022%s: received greet '%s'\u005cn\u0022, __func__,\n+\t\t\t\t pss-\u003eproxy_path);\n+\t\t\t/*\n+\t\t\t * we need to add his pss to our list of configured,\n+\t\t\t * live, joined clients\n+\t\t\t */\n+\t\t\tlws_dll2_add_tail(\u0026pss-\u003elist, \u0026vhd-\u003eclients);\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\t/*\n+\t\t * He's sending us his results... let's collect chunks into the\n+\t\t * pss lwsac before worrying about anything else\n+\t\t */\n+\n+\t\tif (lws_is_first_fragment(wsi))\n+\t\t\tpss-\u003etot \u003d 0;\n+\n+\t\tlws_metrics_om_ac_stash(pss, (const char *)in, len);\n+\n+\t\tif (lws_is_final_fragment(wsi)) {\n+\t\t\tstruct pss *partner_pss;\n+\n+\t\t\tlwsl_info(\u0022%s: ws side received complete msg\u005cn\u0022,\n+\t\t\t\t\t__func__);\n+\n+\t\t\t/* the lwsac is complete */\n+\t\t\tpss-\u003ewalk \u003d pss-\u003eac;\n+\t\t\tpartner_pss \u003d omc_lws_om_get_other_side_pss_client(vhd, pss);\n+\t\t\tif (!partner_pss) {\n+\t\t\t\tlwsl_notice(\u0022%s: no partner A\u005cn\u0022, __func__);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\n+\t\t\t/* indicate to scraper side we want to issue now */\n+\n+\t\t\tp \u003d start;\n+\t\t\tif (lws_add_http_common_headers(partner_pss-\u003ewsi, HTTP_STATUS_OK,\n+\t\t\t\t\t\t\t\u0022application/openmetrics-text; \u0022\n+\t\t\t\t\t\t\t\u0022version\u003d1.0.0; charset\u003dutf-8\u0022,\n+\t\t\t\t\t\t\tpss-\u003etot, \u0026p, end) ||\n+\t\t\t lws_finalize_write_http_header(partner_pss-\u003ewsi,\n+\t\t\t\t\t\t\t start, \u0026p, end))\n+\t\t\t\treturn -1;\n+\n+\t\t\t/* indicate to scraper side we want to issue now */\n+\n+\t\t\tpartner_pss-\u003ewalk \u003d pss-\u003eac;\n+\t\t\tpartner_pss-\u003etrigger \u003d 1;\n+\t\t\tlws_callback_on_writable(partner_pss-\u003ewsi);\n+\t\t}\n+\n+\t\treturn 0;\n+\n+\tcase LWS_CALLBACK_SERVER_WRITEABLE:\n+\t\tif (!pss-\u003etrigger)\n+\t\t\treturn 0;\n+\n+\t\tpss-\u003etrigger \u003d 0;\n+\n+\t\tpartner_pss \u003d omc_lws_om_get_other_side_pss_client(vhd, pss);\n+\t\tif (!partner_pss) {\n+\t\t\tlwsl_err(\u0022%s: no partner\u005cn\u0022, __func__);\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\tlwsl_info(\u0022%s: sending trigger to client\u005cn\u0022, __func__);\n+\n+\t\t*start \u003d 'x';\n+\t\tif (lws_write(wsi, start, 1,\n+\t\t\t (enum lws_write_protocol)LWS_WRITE_TEXT) \u003c 0)\n+\t\t\treturn 1;\n+\n+\t\tlws_validity_confirmed(wsi);\n+\n+\t\treturn 0;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+}\n+#endif\n+\n+#if defined(LWS_WITH_CLIENT) \u0026\u0026 defined(LWS_ROLE_WS)\n+\n+/* 4) ws client that keeps wss connection up to metrics proxy ws server */\n+\n+static int\n+callback_lws_openmetrics_prox_client(struct lws *wsi,\n+\t\t\t\t enum lws_callback_reasons reason,\n+\t\t\t\t void *user, void *in, size_t len)\n+{\n+\tunsigned char buf[1224], *start \u003d buf + LWS_PRE, *p \u003d start,\n+\t\t *end \u003d buf + sizeof(buf) - 1, *ip;\n+\tstruct vhd *vhd \u003d (struct vhd *)lws_protocol_vh_priv_get(\n+\t\t\t\tlws_get_vhost(wsi), lws_get_protocol(wsi));\n+\tstruct lws_context *cx \u003d lws_get_context(wsi);\n+\tstruct pss *pss \u003d (struct pss *)user;\n+\tunsigned int m, wm;\n+\tconst char *cp;\n+\tchar first;\n+\n+\tswitch (reason) {\n+\n+\tcase LWS_CALLBACK_PROTOCOL_INIT:\n+\n+\t\tlwsl_notice(\u0022%s: PROTOCOL_INIT on %s\u005cn\u0022, __func__,\n+\t\t\t\t\tlws_vh_tag(lws_get_vhost(wsi)));\n+\n+\n+\t\t/*\n+\t\t * We get told what to do when we are bound to the vhost\n+\t\t */\n+\t\tvhd \u003d lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),\n+\t\t\t\tlws_get_protocol(wsi), sizeof(struct vhd));\n+\t\tif (!vhd)\n+\t\t\treturn 0;\n+\n+\t\tvhd-\u003ecx \u003d cx;\n+\t\tvhd-\u003evhost \u003d lws_get_vhost(wsi);\n+\n+\t\t/* the proxy server uri */\n+\n+\t\tif (lws_pvo_get_str(in, \u0022ws-server-uri\u0022, \u0026cp)) {\n+\t\t\tlwsl_err(\u0022%s: ws-server-uri pvo required\u005cn\u0022, __func__);\n+\n+\t\t\treturn 1;\n+\t\t}\n+\t\tlws_strncpy(vhd-\u003ews_server_uri, cp, sizeof(vhd-\u003ews_server_uri));\n+\n+\t\t/* how we should be referenced at the proxy */\n+\n+\t\tif (lws_pvo_get_str(in, \u0022metrics-proxy-path\u0022, \u0026cp)) {\n+\t\t\tlwsl_err(\u0022%s: metrics-proxy-path pvo required\u005cn\u0022, __func__);\n+\n+\t\t\treturn 1;\n+\t\t}\n+\t\tlws_strncpy(vhd-\u003emetrics_proxy_path, cp, sizeof(vhd-\u003emetrics_proxy_path));\n+\n+\t\t/* the shared secret to authenticate us as allowed to join */\n+\n+\t\tif (lws_pvo_get_str(in, \u0022ba-secret\u0022, \u0026cp)) {\n+\t\t\tlwsl_err(\u0022%s: ba-secret pvo required\u005cn\u0022, __func__);\n+\n+\t\t\treturn 1;\n+\t\t}\n+\t\tlws_strncpy(vhd-\u003eba_secret, cp, sizeof(vhd-\u003eba_secret));\n+\n+\t\tlwsl_notice(\u0022%s: scheduling connect %s %s %s\u005cn\u0022, __func__,\n+\t\t\t\tvhd-\u003ews_server_uri, vhd-\u003emetrics_proxy_path, vhd-\u003eba_secret);\n+\n+\t\tlws_validity_confirmed(wsi);\n+\t\tlws_sul_schedule(cx, 0, \u0026vhd-\u003esul, omc_connect_client, 1);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_PROTOCOL_DESTROY:\n+\t\tif (vhd)\n+\t\t\tlws_sul_cancel(\u0026vhd-\u003esul);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:\n+\t{\n+\t\tunsigned char **pp \u003d (unsigned char **)in, *pend \u003d (*pp) + len;\n+\t\tchar b[128];\n+\n+\t\t/* authorize ourselves to the metrics proxy using basic auth */\n+\n+\t\tif (lws_http_basic_auth_gen(\u0022metricsclient\u0022, vhd-\u003eba_secret,\n+\t\t\t\t\t b, sizeof(b)))\n+\t\t\tbreak;\n+\n+\t\tif (lws_add_http_header_by_token(wsi,\n+\t\t\t\t\t\t WSI_TOKEN_HTTP_AUTHORIZATION,\n+\t\t\t\t\t\t (unsigned char *)b,\n+\t\t\t\t\t\t (int)strlen(b), pp, pend))\n+\t\t\treturn -1;\n+\n+\t\tbreak;\n+\t}\n+\n+\tcase LWS_CALLBACK_CLIENT_CONNECTION_ERROR:\n+\t\tlwsl_err(\u0022CLIENT_CONNECTION_ERROR: %s\u005cn\u0022,\n+\t\t\t in ? (char *)in : \u0022(null)\u0022);\n+\t\tgoto do_retry;\n+\n+\tcase LWS_CALLBACK_CLIENT_ESTABLISHED:\n+\t\tlwsl_warn(\u0022%s: connected to ws metrics agg server\u005cn\u0022, __func__);\n+\t\tpss-\u003egreet \u003d 1;\n+\t\tlws_callback_on_writable(wsi);\n+\t\tlws_validity_confirmed(wsi);\n+\t\treturn 0;\n+\n+\tcase LWS_CALLBACK_CLIENT_CLOSED:\n+\t\tlwsl_notice(\u0022%s: client closed\u005cn\u0022, __func__);\n+\t\tlwsac_free(\u0026pss-\u003eac);\n+\t\tgoto do_retry;\n+\n+\tcase LWS_CALLBACK_CLIENT_RECEIVE:\n+\t\t/*\n+\t\t * Proxy serverside sends us something to trigger us to create\n+\t\t * our metrics message and send it back over the ws link\n+\t\t */\n+\t\tome_prepare(cx, pss);\n+\t\tpss-\u003ewalk \u003d pss-\u003eac;\n+\t\tlws_callback_on_writable(wsi);\n+\t\tlwsl_info(\u0022%s: dump requested\u005cn\u0022, __func__);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_CLIENT_WRITEABLE:\n+\t\tif (pss-\u003egreet) {\n+\t\t\t/*\n+\t\t\t * At first after establishing the we link, we send a\n+\t\t\t * message indicating to the metrics proxy how we\n+\t\t\t * should be referred to by the scraper to particularly\n+\t\t\t * select to talk to us\n+\t\t\t */\n+\t\t\tlwsl_info(\u0022%s: sending greet '%s'\u005cn\u0022, __func__,\n+\t\t\t\t\tvhd-\u003emetrics_proxy_path);\n+\t\t\tlws_strncpy((char *)start, vhd-\u003emetrics_proxy_path,\n+\t\t\t\t\tsizeof(buf) - LWS_PRE);\n+\t\t\tif (lws_write(wsi, start,\n+\t\t\t\t strlen(vhd-\u003emetrics_proxy_path),\n+\t\t\t\t LWS_WRITE_TEXT) \u003c 0)\n+\t\t\t\treturn 1;\n+\n+\t\t\tlws_validity_confirmed(wsi);\n+\n+\t\t\tpss-\u003egreet \u003d 0;\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\tif (!pss-\u003ewalk)\n+\t\t\treturn 0;\n+\n+\t\t/*\n+\t\t * We send the metrics dump in a single logical ws message,\n+\t\t * using ws fragmentation to split it around 1 mtu boundary\n+\t\t * and keep coming back until it's finished\n+\t\t */\n+\n+\t\tfirst \u003d pss-\u003ewalk \u003d\u003d pss-\u003eac;\n+\n+\t\tdo {\n+\t\t\tip \u003d (uint8_t *)pss-\u003ewalk +\n+\t\t\t\tlwsac_sizeof(pss-\u003ewalk \u003d\u003d pss-\u003eac) + LWS_PRE;\n+\t\t\tm \u003d (unsigned int)((ip[0] \u003c\u003c 8) | ip[1]);\n+\n+\t\t\t/* coverity */\n+\t\t\tif (m \u003e lwsac_get_tail_pos(pss-\u003ewalk) -\n+\t\t\t\tlwsac_sizeof(pss-\u003ewalk \u003d\u003d pss-\u003eac)) {\n+\t\t\t\tlwsl_err(\u0022%s: size blow\u005cn\u0022, __func__);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\n+\t\t\tif (lws_ptr_diff_size_t(end, p) \u003c m)\n+\t\t\t\tbreak;\n+\n+\t\t\tmemcpy(p, ip + 2, m);\n+\t\t\tp +\u003d m;\n+\n+\t\t\tpss-\u003ewalk \u003d lwsac_get_next(pss-\u003ewalk);\n+\t\t} while (pss-\u003ewalk);\n+\n+\t\tif (!lws_ptr_diff_size_t(p, start)) {\n+\t\t\tlwsl_err(\u0022%s: stuck\u005cn\u0022, __func__);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\twm \u003d (unsigned int)lws_write_ws_flags(LWS_WRITE_TEXT, first,\n+\t\t\t\t\t\t !pss-\u003ewalk);\n+\n+\t\tif (lws_write(wsi, start, lws_ptr_diff_size_t(p, start),\n+\t\t\t (enum lws_write_protocol)wm) \u003c 0) {\n+\t\t\tlwsl_notice(\u0022%s: write fail\u005cn\u0022, __func__);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tlws_validity_confirmed(wsi);\n+\t\tlwsl_info(\u0022%s: forwarded %d\u005cn\u0022, __func__, lws_ptr_diff(p, start));\n+\n+\t\tif (!pss-\u003ewalk) {\n+\t\t\tlwsl_info(\u0022%s: dump send completed\u005cn\u0022, __func__);\n+\t\t\tlwsac_free(\u0026pss-\u003eac);\n+\t\t} else\n+\t\t\tlws_callback_on_writable(wsi);\n+\n+\t\treturn 0;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+\n+do_retry:\n+\tif (!lws_retry_sul_schedule(cx, 0, \u0026vhd-\u003esul, \u0026retry,\n+\t\t\t\t omc_connect_client, \u0026vhd-\u003eretry_count))\n+\t\treturn 0;\n+\n+\tvhd-\u003eretry_count \u003d 0;\n+\tlws_retry_sul_schedule(cx, 0, \u0026vhd-\u003esul, \u0026retry,\n+\t\t\t omc_connect_client, \u0026vhd-\u003eretry_count);\n+\n+\treturn 0;\n+}\n+#endif\n+\n+\n+LWS_VISIBLE const struct lws_protocols lws_openmetrics_export_protocols[] \u003d {\n+#if defined(LWS_WITH_SERVER)\n+\t{ /* for scraper directly: http export on listen socket */\n+\t\t\u0022lws-openmetrics\u0022,\n+\t\tcallback_lws_openmetrics_export,\n+\t\tsizeof(struct pss),\n+\t\t1024,\n+\t},\n+\t{ /* for scraper via ws proxy: http export on listen socket */\n+\t\t\u0022lws-openmetrics-prox-agg\u0022,\n+\t\tcallback_lws_openmetrics_prox_agg,\n+\t\tsizeof(struct pss),\n+\t\t1024,\n+\t},\n+\t{ /* metrics proxy server side: ws server for clients to connect to */\n+\t\t\u0022lws-openmetrics-prox-server\u0022,\n+\t\tcallback_lws_openmetrics_prox_server,\n+\t\tsizeof(struct pss),\n+\t\t1024,\n+\t},\n+#endif\n+#if defined(LWS_WITH_CLIENT) \u0026\u0026 defined(LWS_ROLE_WS)\n+\t{ /* client to metrics proxy: ws client to connect to metrics proxy*/\n+\t\t\u0022lws-openmetrics-prox-client\u0022,\n+\t\tcallback_lws_openmetrics_prox_client,\n+\t\tsizeof(struct pss),\n+\t\t1024,\n+\t},\n+#endif\n+};\n+\n+LWS_VISIBLE const lws_plugin_protocol_t lws_openmetrics_export \u003d {\n+\t.hdr \u003d {\n+\t\t\u0022lws OpenMetrics export\u0022,\n+\t\t\u0022lws_protocol_plugin\u0022,\n+\t\tLWS_BUILD_HASH,\n+\t\tLWS_PLUGIN_API_MAGIC\n+\t},\n+\n+\t.protocols \u003d lws_openmetrics_export_protocols,\n+\t.count_protocols \u003d LWS_ARRAY_SIZE(lws_openmetrics_export_protocols),\n+};\ndiff --git a/plugins/protocol_lws_server_status.c b/plugins/protocol_lws_server_status.c\ndeleted file mode 100644\nindex 497f59e..0000000\n--- a/plugins/protocol_lws_server_status.c\n+++ /dev/null\n@@ -1,218 +0,0 @@\n-/*\n- * libwebsockets-test-server - libwebsockets test implementation\n- *\n- * Written in 2010-2019 by Andy Green \u003candy@warmcat.com\u003e\n- *\n- * This file is made available under the Creative Commons CC0 1.0\n- * Universal Public Domain Dedication.\n- *\n- * The person who associated a work with this deed has dedicated\n- * the work to the public domain by waiving all of his or her rights\n- * to the work worldwide under copyright law, including all related\n- * and neighboring rights, to the extent allowed by law. You can copy,\n- * modify, distribute and perform the work, even for commercial purposes,\n- * all without asking permission.\n- *\n- * The test apps are intended to be adapted for use in your code, which\n- * may be proprietary. So unlike the library itself, they are licensed\n- * Public Domain.\n- */\n-\n-#define LWS_DLL\n-#define LWS_INTERNAL\n-#include \u003clibwebsockets.h\u003e\n-#include \u003cstring.h\u003e\n-#include \u003cstdlib.h\u003e\n-#include \u003csys/types.h\u003e\n-#include \u003csys/stat.h\u003e\n-#include \u003cfcntl.h\u003e\n-#include \u003cstdio.h\u003e\n-\n-struct lws_ss_filepath {\n-\tstruct lws_ss_filepath *next;\n-\tchar filepath[128];\n-};\n-\n-struct lws_ss_dumps {\n-\tchar buf[32768];\n-\tint length;\n-};\n-\n-struct pss {\n-\tint ver;\n-\tint pos;\n-};\n-\n-struct vhd {\n-\tstruct lws_context *context;\n-\tstruct lws_vhost *vhost;\n-\tconst struct lws_protocols *protocol;\n-\tlws_sorted_usec_list_t sul;\n-\tint hide_vhosts;\n-\tint tow_flag;\n-\tint period_s;\n-\tint clients;\n-\tstruct lws_ss_dumps d;\n-\tstruct lws_ss_filepath *fp;\n-};\n-\n-static const struct lws_protocols protocols[1];\n-\n-static void\n-update(struct lws_sorted_usec_list *sul)\n-{\n-\tstruct vhd *v \u003d lws_container_of(sul, struct vhd, sul);\n-\tstruct lws_ss_filepath *fp;\n-\tchar contents[256], pure[256], *p \u003d v-\u003ed.buf + LWS_PRE,\n-\t *end \u003d v-\u003ed.buf + sizeof(v-\u003ed.buf) - LWS_PRE - 1;\n-\tint n, first \u003d 1, fd;\n-\n-\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022{\u005c\u0022i\u005c\u0022:\u0022);\n-\tp +\u003d lws_json_dump_context(v-\u003econtext, p, lws_ptr_diff(end, p),\n-\t\t\t\t v-\u003ehide_vhosts);\n-\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022, \u005c\u0022files\u005c\u0022: [\u0022);\n-\n-\tfp \u003d v-\u003efp;\n-\twhile (fp) {\n-\t\tif (!first)\n-\t\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022,\u0022);\n-\n-\t\tstrcpy(pure, \u0022(unknown)\u0022);\n-\t\tfd \u003d lws_open(fp-\u003efilepath, LWS_O_RDONLY);\n-\t\tif (fd \u003e\u003d 0) {\n-\t\t\tn \u003d (int)read(fd, contents, sizeof(contents) - 1);\n-\t\t\tclose(fd);\n-\t\t\tif (n \u003e\u003d 0) {\n-\t\t\t\tcontents[n] \u003d '\u005c0';\n-\t\t\t\tlws_json_purify(pure, contents, sizeof(pure), NULL);\n-\t\t\t}\n-\t\t}\n-\n-\t\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p),\n-\t\t\t\t\u0022{\u005c\u0022path\u005c\u0022:\u005c\u0022%s\u005c\u0022,\u005c\u0022val\u005c\u0022:\u005c\u0022%s\u005c\u0022}\u0022,\n-\t\t\t\t\tfp-\u003efilepath, pure);\n-\t\tfirst \u003d 0;\n-\n-\t\tfp \u003d fp-\u003enext;\n-\t}\n-\tp +\u003d lws_snprintf(p, lws_ptr_diff_size_t(end, p), \u0022]}\u0022);\n-\tv-\u003ed.length \u003d lws_ptr_diff(p, (v-\u003ed.buf + LWS_PRE));\n-\n-\tlws_callback_on_writable_all_protocol(v-\u003econtext, \u0026protocols[0]);\n-\n-\tlws_sul_schedule(v-\u003econtext, 0, \u0026v-\u003esul, update, v-\u003eperiod_s * LWS_US_PER_SEC);\n-}\n-\n-static int\n-callback_lws_server_status(struct lws *wsi, enum lws_callback_reasons reason,\n-\t\t\t void *user, void *in, size_t len)\n-{\n-\tconst struct lws_protocol_vhost_options *pvo \u003d\n-\t\t\t(const struct lws_protocol_vhost_options *)in;\n-\tstruct vhd *v \u003d (struct vhd *)\n-\t\t\tlws_protocol_vh_priv_get(lws_get_vhost(wsi),\n-\t\t\t\t\tlws_get_protocol(wsi));\n-\tstruct lws_ss_filepath *fp, *fp1, **fp_old;\n-\tint m;\n-\n-\tswitch (reason) {\n-\n-\tcase LWS_CALLBACK_ESTABLISHED:\n-\t\tlwsl_info(\u0022%s: LWS_CALLBACK_ESTABLISHED\u005cn\u0022, __func__);\n-\t\tif (!v-\u003eclients++) {\n-\t\t\tlws_sul_schedule(lws_get_context(wsi), 0, \u0026v-\u003esul, update, 1);\n-\t\t\tlwsl_info(\u0022%s: starting updates\u005cn\u0022, __func__);\n-\t\t}\n-\t\tbreak;\n-\n-\tcase LWS_CALLBACK_CLOSED:\n-\t\tif (!--v-\u003eclients)\n-\t\t\tlwsl_notice(\u0022%s: stopping updates\u005cn\u0022, __func__);\n-\n-\t\tbreak;\n-\n-\tcase LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */\n-\t\tif (v)\n-\t\t\tbreak;\n-\n-\t\tlws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),\n-\t\t\t\t\t lws_get_protocol(wsi),\n-\t\t\t\t\t sizeof(struct vhd));\n-\t\tv \u003d (struct vhd *)lws_protocol_vh_priv_get(lws_get_vhost(wsi),\n-\t\t\t\t\t\t\t lws_get_protocol(wsi));\n-\n-\t\tfp_old \u003d \u0026v-\u003efp;\n-\n-\t\twhile (pvo) {\n-\t\t\tif (!strcmp(pvo-\u003ename, \u0022hide-vhosts\u0022))\n-\t\t\t\tv-\u003ehide_vhosts \u003d atoi(pvo-\u003evalue);\n-\t\t\tif (!strcmp(pvo-\u003ename, \u0022update-ms\u0022))\n-\t\t\t\tv-\u003eperiod_s \u003d (atoi(pvo-\u003evalue) + 500) / 1000;\n-\t\t\telse\n-\t\t\t\tv-\u003eperiod_s \u003d 5;\n-\t\t\tif (!strcmp(pvo-\u003ename, \u0022filepath\u0022)) {\n-\t\t\t\tfp \u003d malloc(sizeof(*fp));\n-\t\t\t\tif (!fp)\n-\t\t\t\t\treturn -1;\n-\t\t\t\tfp-\u003enext \u003d NULL;\n-\t\t\t\tlws_snprintf(\u0026fp-\u003efilepath[0],\n-\t\t\t\t\t sizeof(fp-\u003efilepath), \u0022%s\u0022,\n-\t\t\t\t\t pvo-\u003evalue);\n-\t\t\t\t*fp_old \u003d fp;\n-\t\t\t\tfp_old \u003d \u0026fp-\u003enext;\n-\t\t\t}\n-\t\t\tpvo \u003d pvo-\u003enext;\n-\t\t}\n-\t\tv-\u003econtext \u003d lws_get_context(wsi);\n-\t\tv-\u003evhost \u003d lws_get_vhost(wsi);\n-\t\tv-\u003eprotocol \u003d lws_get_protocol(wsi);\n-\n-\t\tlws_sul_schedule(lws_get_context(wsi), 0, \u0026v-\u003esul, update, 1);\n-\t\tbreak;\n-\n-\tcase LWS_CALLBACK_PROTOCOL_DESTROY: /* per vhost */\n-\t\tif (!v)\n-\t\t\tbreak;\n-\t\tfp \u003d v-\u003efp;\n-\t\twhile (fp) {\n-\t\t\tfp1\u003d fp-\u003enext;\n-\t\t\tfree(fp);\n-\t\t\tfp \u003d fp1;\n-\t\t}\n-\t\tbreak;\n-\n-\tcase LWS_CALLBACK_SERVER_WRITEABLE:\n-\t\tm \u003d lws_write(wsi, (unsigned char *)v-\u003ed.buf + LWS_PRE,\n-\t\t\t (size_t)v-\u003ed.length, LWS_WRITE_TEXT);\n-\t\tif (m \u003c 0)\n-\t\t\treturn -1;\n-\t\tbreak;\n-\n-\tdefault:\n-\t\tbreak;\n-\t}\n-\n-\treturn 0;\n-}\n-\n-static const struct lws_protocols protocols[] \u003d {\n-\t{\n-\t\t\u0022lws-server-status\u0022,\n-\t\tcallback_lws_server_status,\n-\t\tsizeof(struct pss),\n-\t\t1024,\n-\t},\n-};\n-\n-LWS_VISIBLE const lws_plugin_protocol_t lws_server_status \u003d {\n-\t.hdr \u003d {\n-\t\t\u0022lws server status\u0022,\n-\t\t\u0022lws_protocol_plugin\u0022,\n-\t\tLWS_PLUGIN_API_MAGIC\n-\t},\n-\n-\t.protocols \u003d protocols,\n-\t.count_protocols \u003d LWS_ARRAY_SIZE(protocols),\n-\t.extensions \u003d NULL,\n-\t.count_extensions \u003d 0,\n-};\ndiff --git a/plugins/server-status.html b/plugins/server-status.html\ndeleted file mode 100644\nindex 0c03863..0000000\n--- a/plugins/server-status.html\n+++ /dev/null\n@@ -1,25 +0,0 @@\n-\u003c!DOCTYPE html\u003e\n-\u003chtml lang\u003d\u0022en\u0022\u003e\n-\u003chead\u003e\n- \u003cmeta charset\u003dutf-8 http-equiv\u003d\u0022Content-Language\u0022 content\u003d\u0022en\u0022/\u003e\n- \u003clink rel\u003d\u0022stylesheet\u0022 type\u003d\u0022text/css\u0022 href\u003d\u0022server-status.css\u0022/\u003e\n- \u003cscript src\u003d\u0022/lws-common.js\u0022\u003e\u003c/script\u003e\n- \u003cscript type\u003d'text/javascript' src\u003d'server-status.js'\u003e\u003c/script\u003e\n- \u003ctitle\u003eLWS Server Status\u003c/title\u003e\n-\u003c/head\u003e\n-\n-\u003cbody\u003e\n-\u003cheader\u003e\u003c/header\u003e\n-\u003carticle\u003e\n-\n-\u003ctable\u003e\n-\u003ctr\u003e\u003ctd\u003e\u003cimg src\u003d\u0022./lwsws-logo.png\u0022\u003e\u003c/td\u003e\u003ctd\u003e\n-\u003cspan id\u003dtitle class\u003dtitle\u003eServer status\u003c/span\u003e\u003c/td\u003e\u003c/tr\u003e\n-\u003ctr\u003e\u003ctd align\u003dcenter colspan\u003d2\u003e\n-\u003cdiv id\u003d\u0022conninfo\u0022\u003e...\u003c/div\u003e\n-\u003cdiv id\u003d\u0022json\u0022\u003e\u003c/div\u003e\n-\u003c/td\u003e\u003c/tr\u003e\n-\u003c/table\u003e\n-\u003c/article\u003e\n-\u003c/body\u003e\n-\u003c/html\u003e\ndiff --git a/plugins/server-status.js b/plugins/server-status.js\ndeleted file mode 100644\nindex ddc7796..0000000\n--- a/plugins/server-status.js\n+++ /dev/null\n@@ -1,249 +0,0 @@\n-(function() {\n-\n-/*\n- * We display untrusted stuff in html context... reject anything\n- * that has HTML stuff in it\n- */\n-\n-function san(s)\n-{\n-\tif (s.search(\u0022\u003c\u0022) !\u003d\u003d -1)\n-\t\treturn \u0022invalid string\u0022;\n-\t\n-\treturn s;\n-}\n-\n-function humanize(s)\n-{\n-\tvar i \u003d parseInt(s, 10);\n-\t\n-\tif (i \u003e\u003d (1024 * 1024 * 1024))\n-\t\treturn (i / (1024 * 1024 * 1024)).toFixed(3) + \u0022Gi\u0022;\n-\t\n-\tif (i \u003e\u003d (1024 * 1024))\n-\t\treturn (i / (1024 * 1024)).toFixed(3) + \u0022Mi\u0022;\n-\t\n-\tif (i \u003e 1024)\n-\t\treturn (i / 1024).toFixed(3) + \u0022Ki\u0022;\n-\t\n-\treturn s;\n-}\n-\n-function get_appropriate_ws_url()\n-{\n-\tvar pcol;\n-\tvar u \u003d document.URL;\n-\n-\t/*\n-\t * We open the websocket encrypted if this page came on an\n-\t * https:// url itself, otherwise unencrypted\n-\t */\n-\n-\tif (u.substring(0, 5) \u003d\u003d\u003d \u0022https\u0022) {\n-\t\tpcol \u003d \u0022wss://\u0022;\n-\t\tu \u003d u.substr(8);\n-\t} else {\n-\t\tpcol \u003d \u0022ws://\u0022;\n-\t\tif (u.substring(0, 4) \u003d\u003d\u003d \u0022http\u0022)\n-\t\t\tu \u003d u.substr(7);\n-\t}\n-\n-\tu \u003d u.split(\u0022/\u0022);\n-\n-\t/* + \u0022/xxx\u0022 bit is for IE10 workaround */\n-\n-\treturn pcol + u[0] + \u0022/xxx\u0022;\n-}\n-\n-\n-\tvar socket_status, jso, s;\n-\n-function ws_open_server_status()\n-{\t\n-\tsocket_status \u003d new WebSocket(get_appropriate_ws_url(),\n-\t\t\t\t \u0022lws-server-status\u0022);\n-\n-\ttry {\n-\t\tsocket_status.onopen \u003d function() {\n-\t\t\tdocument.getElementById(\u0022title\u0022).innerHTML \u003d \u0022Server Status (Active)\u0022;\n-\t\t\tlws_gray_out(false);\n-\t\t};\n-\n-\t\tsocket_status.onmessage \u003dfunction got_packet(msg) {\n-\t\t\tvar u, ci, n;\n-\t\t\t// document.getElementById(\u0022json\u0022).innerHTML \u003d \u0022\u003cpre\u003e\u0022+msg.data+\u0022\u003c/pre\u003e\u0022;\n-\t\t\tif (msg.data.length \u003c 100)\n-\t\t\t\treturn;\n-\t\t\tjso \u003d JSON.parse(msg.data);\n-\t\t\tu \u003d parseInt(san(jso.i.uptime), 10);\n-\n-\t\t\tif (parseInt(jso.i.contexts[0].deprecated, 10) \u003d\u003d\u003d 0)\n-\t\t\t\ts \u003d \u0022\u003ctable\u003e\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd class\u003d\u005c\u0022c0\u005c\u0022\u003e\u0022;\n-\t\t\telse\n-\t\t\t\ts \u003d \u0022\u003ctable\u003e\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd class\u003d\u005c\u0022dc0\u005c\u0022\u003e\u0022;\n-\t\t\ts +\u003d\n-\t\t\t \u0022Server\u003c/td\u003e\u003ctd\u003e\u0022 +\n-\t\t\t \u0022\u003cspan class\u003d\u005c\u0022sn\u005c\u0022\u003eServer Version:\u003c/span\u003e \u003cspan class\u003d\u005c\u0022v\u005c\u0022\u003e\u0022 +\n-\t\t\t san(jso.i.version) + \u0022\u003c/span\u003e\u003cbr\u003e\u0022 +\n-\t\t\t \u0022\u003cspan class\u003d\u005c\u0022sn\u005c\u0022\u003eHost Uptime:\u003c/span\u003e \u003cspan class\u003d\u005c\u0022v\u005c\u0022\u003e\u0022 +\n-\t\t\t ((u / (24 * 3600)) | 0) + \u0022d \u0022 +\n-\t\t\t (((u % (24 * 3600)) / 3600) | 0) + \u0022h \u0022 +\n-\t\t\t (((u % 3600) / 60) | 0) + \u0022m\u003c/span\u003e\u0022;\n-\t\t\tif (jso.i.l1)\n-\t\t\t\ts \u003d s + \u0022, \u003cspan class\u003d\u005c\u0022sn\u005c\u0022\u003eHost Load:\u003c/span\u003e \u003cspan class\u003d\u005c\u0022v\u005c\u0022\u003e\u0022 + san(jso.i.l1) + \u0022 \u0022;\n-\t\t\tif (jso.i.l2)\n-\t\t\t\ts \u003d s + san(jso.i.l2) + \u0022 \u0022;\n-\t\t\tif (jso.i.l3)\n-\t\t\t\ts \u003d s + san(jso.i.l3);\n-\t\t\tif (jso.i.l1)\n-\t\t\t\ts \u003ds + \u0022\u003c/span\u003e\u0022;\n-\t\t\t\t\n-\t\t\tif (jso.i.statm) {\n-\t\t\t\tvar sm \u003d jso.i.statm.split(\u0022 \u0022);\n-\t\t\t\ts +\u003d \u0022, \u003cspan class\u003d\u005c\u0022sn\u005c\u0022\u003eVirt stack + heap Usage:\u003c/span\u003e \u003cspan class\u003d\u005c\u0022v\u005c\u0022\u003e\u0022 +\n-\t\t\t\t\thumanize(parseInt(sm[5], 10) * 4096) + \u0022B\u003c/span\u003e\u0022;\n-\t\t\t}\n-\t\t\ts +\u003d \u0022, \u003cspan class\u003d\u005c\u0022sn\u005c\u0022\u003elws heap usage:\u003c/span\u003e \u003cspan class\u003d\u005c\u0022v\u005c\u0022\u003e\u0022 +\n-\t\t\thumanize(jso.i.heap) + \u0022B\u003c/span\u003e\u0022;\n-\n-\t\t\t\t\n-\t\t\tfor (n \u003d 0; n \u003c jso.files.length; n++) {\n-\t\t\t\ts +\u003d \u0022\u003cbr\u003e\u003cspan class\u003dn\u003e\u0022 + san(jso.files[n].path) + \u0022:\u003c/span\u003e\u003cbr\u003e \u0022 + san(jso.files[n].val);\n-\t\t\t}\n-\t\t\ts +\u003d \u0022\u003c/td\u003e\u003c/tr\u003e\u0022;\n-\n-\t\t\tfor (ci \u003d 0; ci \u003c jso.i.contexts.length; ci++) {\n-\n-\t\t\t\tif (parseInt(jso.i.contexts[ci].deprecated, 10) \u003d\u003d\u003d 0)\n-\t\t\t\t\ts +\u003d \u0022\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd class\u003d\u005c\u0022c\u005c\u0022\u003e\u0022 +\n-\t\t\t\t\t \u0022Active Context\u003c/td\u003e\u003ctd\u003e\u0022;\n-\t\t\t\telse\n-\t\t\t\t\ts +\u003d \u0022\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd class\u003d\u005c\u0022c1\u005c\u0022\u003e\u0022 +\n-\t\t\t\t\t \u0022Deprecated Context \u0022 + ci + \u0022\u003c/td\u003e\u003ctd\u003e\u0022;\n-\n-\t\t\t\t u \u003d parseInt(san(jso.i.contexts[ci].context_uptime), 10);\n-\t \t\t\t s +\u003d \u0022\u003cspan class\u003dn\u003eServer Uptime:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 +\n-\t\t\t\t ((u / (24 * 3600)) | 0) + \u0022d \u0022 +\n-\t\t\t\t (((u % (24 * 3600)) / 3600) | 0) + \u0022h \u0022 +\n-\t\t\t\t (((u % 3600) / 60) | 0) + \u0022m\u003c/span\u003e\u0022;\n-\n-\t\t\t\ts \u003d s +\n-\t\t\t\t \u0022\u003cbr\u003e\u0022 +\n-\t\t\t\t \u0022\u003cspan class\u003dn\u003eTagged objects alive:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].wsi_alive) + \u0022\u003c/span\u003e\u003cbr\u003e\u0022 +\n-\t\t\t \t \u0022\u003cspan class\u003dn\u003eTotal Rx:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + humanize(san(jso.i.contexts[ci].rx)) +\u0022B\u003c/span\u003e, \u0022 +\n-\t\t\t \t \u0022\u003cspan class\u003dn\u003eTotal Tx:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + humanize(san(jso.i.contexts[ci].tx)) +\u0022B\u003c/span\u003e\u003cbr\u003e\u0022 +\n-\t\t\t \t \n-\t\t\t \t \u0022\u003cspan class\u003dn\u003eCONNECTIONS: HTTP/1.x:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].h1_conn) +\u0022\u003c/span\u003e, \u0022 +\n-\t\t\t \t \u0022\u003cspan class\u003dn\u003eWebsocket:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].ws_upg) +\u0022\u003c/span\u003e, \u0022 +\n-\t\t\t \t \u0022\u003cspan class\u003dn\u003eH2 upgrade:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].h2_upg) +\u0022\u003c/span\u003e, \u0022 +\n-\t\t\t \t \u0022\u003cspan class\u003dn\u003eH2 ALPN:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].h2_alpn) +\u0022\u003c/span\u003e, \u0022 +\n-\t\t\t \t \u0022\u003cspan class\u003dn\u003eRejected:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].rejected) +\u0022\u003c/span\u003e\u003cbr\u003e\u0022 +\n-\n-\t\t\t \t \u0022\u003cspan class\u003dn\u003eTRANSACTIONS: HTTP/1.x:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].h1_trans) + \u0022\u003c/span\u003e, \u0022 +\n-\t\t\t \t \u0022\u003cspan class\u003dn\u003eH2:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].h2_trans) +\u0022\u003c/span\u003e, \u0022 +\n-\t\t\t \t \u0022\u003cspan class\u003dn\u003eTotal H2 substreams:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].h2_subs) +\u0022\u003c/span\u003e\u003cbr\u003e\u0022 +\n-\n-\t\t\t\t \u0022\u003cspan class\u003dn\u003eCGI: alive:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].cgi_alive) + \u0022\u003c/span\u003e, \u0022 +\n-\t\t\t\t \u0022\u003cspan class\u003dn\u003espawned:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].cgi_spawned) +\n-\t\t\t\t \u0022\u003c/span\u003e\u003ctable\u003e\u0022;\n-\t\t\t\t\n-\t\t\t\tfor (n \u003d 0; n \u003c jso.i.contexts[ci].pt.length; n++) {\n-\n-\t\t\t\t\tif (parseInt(jso.i.contexts[ci].deprecated, 10) \u003d\u003d\u003d 0)\n-\t\t\t\t\t\ts +\u003d \u0022\u003ctr\u003e\u003ctd\u003e\u0026nbsp;\u0026nbsp;\u003c/td\u003e\u003ctd class\u003d\u005c\u0022l\u005c\u0022\u003eservice thread \u0022 + (n + 1);\n-\t\t\t\t\telse\n-\t\t\t\t\t\ts +\u003d \u0022\u003ctr\u003e\u003ctd\u003e\u0026nbsp;\u0026nbsp;\u003c/td\u003e\u003ctd class\u003d\u005c\u0022dl\u005c\u0022\u003eservice thread \u0022 + (n + 1);\n-\t\t\t\t\ts +\u003d \u0022\u003c/td\u003e\u003ctd\u003e\u0022 +\n-\t\t\t\t\t\u0022\u003cspan class\u003dn\u003efds:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].pt[n].fds_count) + \u0022 / \u0022 +\n-\t\t\t\t\t\t san(jso.i.contexts[ci].pt_fd_max) + \u0022\u003c/span\u003e, \u0022;\n-\t\t\t\t\ts \u003d s + \u0022\u003cspan class\u003dn\u003eah pool:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].pt[n].ah_pool_inuse) + \u0022 / \u0022 +\n-\t\t\t\t\t\t san(jso.i.contexts[ci].ah_pool_max) + \u0022\u003c/span\u003e, \u0022 +\n-\t\t\t\t\t\u0022\u003cspan class\u003dn\u003eah waiting list:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].pt[n].ah_wait_list);\n-\t\n-\t\t\t\t\ts \u003d s + \u0022\u003c/span\u003e\u003c/td\u003e\u003c/tr\u003e\u0022;\n-\t\n-\t\t\t\t}\n-\t\t\t\tfor (n \u003d 0; n \u003c jso.i.contexts[ci].vhosts.length; n++) {\n-\t\t\t\t\tif (parseInt(jso.i.contexts[ci].deprecated, 10) \u003d\u003d\u003d 0)\n-\t\t\t\t\t\ts +\u003d \u0022\u003ctr\u003e\u003ctd\u003e\u0026nbsp;\u0026nbsp;\u003c/td\u003e\u003ctd class\u003d\u005c\u0022l\u005c\u0022\u003evhost \u0022 + (n + 1);\n-\t\t\t\t\telse\n-\t\t\t\t\t\ts +\u003d \u0022\u003ctr\u003e\u003ctd\u003e\u0026nbsp;\u0026nbsp;\u003c/td\u003e\u003ctd class\u003d\u005c\u0022dl\u005c\u0022\u003evhost \u0022 + (n + 1);\n-\t\t\t\t\ts +\u003d \u0022\u003c/td\u003e\u003ctd\u003e\u003cspan class\u003d\u005c\u0022mountname\u005c\u0022\u003e\u0022;\n-\t\t\t\t\tif (jso.i.contexts[ci].vhosts[n].use_ssl \u003d\u003d\u003d \u00221\u0022)\n-\t\t\t\t\t\ts \u003d s + \u0022https://\u0022;\n-\t\t\t\t\telse\n-\t\t\t\t\t\ts \u003d s + \u0022http://\u0022;\n-\t\t\t\t\ts \u003d s + san(jso.i.contexts[ci].vhosts[n].name) + \u0022:\u0022 +\n-\t\t\t\t\t\tsan(jso.i.contexts[ci].vhosts[n].port) + \u0022\u003c/span\u003e\u0022;\n-\t\t\t\t\tif (jso.i.contexts[ci].vhosts[n].sts \u003d\u003d\u003d \u00221\u0022)\n-\t\t\t\t\t\ts \u003d s + \u0022 (STS)\u0022;\n-\t\t\t\t\ts \u003d s +\u0022\u003cbr\u003e\u0022 +\n-\t\t\t\t\t\n-\t\t\t\t\t \u0022\u003cspan class\u003dn\u003eTotal Rx:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + humanize(san(jso.i.contexts[ci].vhosts[n].rx)) +\u0022B\u003c/span\u003e, \u0022 +\n-\t\t\t\t\t \u0022\u003cspan class\u003dn\u003eTotal Tx:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + humanize(san(jso.i.contexts[ci].vhosts[n].tx)) +\u0022B\u003c/span\u003e\u003cbr\u003e\u0022 +\n-\t\t\t\t\t \n-\t\t\t\t\t \u0022\u003cspan class\u003dn\u003eCONNECTIONS: HTTP/1.x:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].vhosts[n].h1_conn) +\u0022\u003c/span\u003e, \u0022 +\n-\t\t\t\t\t \u0022\u003cspan class\u003dn\u003eWebsocket:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].vhosts[n].ws_upg) +\u0022\u003c/span\u003e, \u0022 +\n-\t\t\t\t\t \u0022\u003cspan class\u003dn\u003eH2 upgrade:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].vhosts[n].h2_upg) +\u0022\u003c/span\u003e, \u0022 +\n-\t\t\t\t\t \u0022\u003cspan class\u003dn\u003eH2 ALPN:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].vhosts[n].h2_alpn) +\u0022\u003c/span\u003e, \u0022 +\n-\t\t\t\t\t \u0022\u003cspan class\u003dn\u003eRejected:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].vhosts[n].rejected) +\u0022\u003c/span\u003e\u003cbr\u003e\u0022 +\n-\t\t\t\t\t\n-\t\t\t\t\t \u0022\u003cspan class\u003dn\u003eTRANSACTIONS: HTTP/1.x:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].vhosts[n].h1_trans) + \u0022\u003c/span\u003e, \u0022 +\n-\t\t\t\t\t \u0022\u003cspan class\u003dn\u003eH2:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].vhosts[n].h2_trans) +\u0022\u003c/span\u003e, \u0022 +\n-\t\t\t\t\t \u0022\u003cspan class\u003dn\u003eTotal H2 substreams:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 + san(jso.i.contexts[ci].vhosts[n].h2_subs) +\u0022\u003c/span\u003e\u003cbr\u003e\u0022;\n-\t\t\t\t\t\n-\t\t\t\t\tif (jso.i.contexts[ci].vhosts[n].mounts) {\n-\t\t\t\t\t\ts \u003d s + \u0022\u003ctable\u003e\u003ctr\u003e\u003ctd class\u003dt\u003eMountpoint\u003c/td\u003e\u003ctd class\u003dt\u003eOrigin\u003c/td\u003e\u003ctd class\u003dt\u003eCache Policy\u003c/td\u003e\u003c/tr\u003e\u0022;\n-\t\n-\t\t\t\t\t\tvar m;\n-\t\t\t\t\t\tfor (m \u003d 0; m \u003c jso.i.contexts[ci].vhosts[n].mounts.length; m++) {\n-\t\t\t\t\t\t\ts \u003d s + \u0022\u003ctr\u003e\u003ctd\u003e\u0022;\n-\t\t\t\t\t\t\ts \u003d s + \u0022\u003cspan class\u003d\u005c\u0022m1\u005c\u0022\u003e\u0022 + san(jso.i.contexts[ci].vhosts[n].mounts[m].mountpoint) +\n-\t\t\t\t\t\t\t\t\u0022\u003c/span\u003e\u003c/td\u003e\u003ctd\u003e\u003cspan class\u003d\u005c\u0022m2\u005c\u0022\u003e\u0022 +\n-\t\t\t\t\t\t\t\tsan(jso.i.contexts[ci].vhosts[n].mounts[m].origin) +\n-\t\t\t\t\t\t\t\t\u0022\u003c/span\u003e\u003c/td\u003e\u003ctd\u003e\u0022;\n-\t\t\t\t\t\t\tif (parseInt(san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_max_age), 10))\n-\t\t\t\t\t\t\t\ts \u003d s + \u0022\u003cspan class\u003dn\u003emax-age:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 +\n-\t\t\t\t\t\t\t\tsan(jso.i.contexts[ci].vhosts[n].mounts[m].cache_max_age) +\n-\t\t\t\t\t\t\t\t\u0022\u003c/span\u003e, \u003cspan class\u003dn\u003ereuse:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 +\n-\t\t\t\t\t\t\t\tsan(jso.i.contexts[ci].vhosts[n].mounts[m].cache_reuse) +\n-\t\t\t\t\t\t\t\t\u0022\u003c/span\u003e, \u003cspan class\u003dn\u003ereval:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 +\n-\t\t\t\t\t\t\t\tsan(jso.i.contexts[ci].vhosts[n].mounts[m].cache_revalidate) +\n-\t\t\t\t\t\t\t\t\u0022\u003c/span\u003e, \u003cspan class\u003dn\u003einter:\u003c/span\u003e \u003cspan class\u003dv\u003e\u0022 +\n-\t\t\t\t\t\t\t\tsan(jso.i.contexts[ci].vhosts[n].mounts[m].cache_intermediaries);\n-\t\t\t\t\t\t\ts \u003d s + \u0022\u003c/span\u003e\u003c/td\u003e\u003c/tr\u003e\u0022;\n-\t\t\t\t\t\t}\n-\t\t\t\t\t\ts \u003d s + \u0022\u003c/table\u003e\u0022;\n-\t\t\t\t\t}\n-\t\t\t\t\ts \u003d s + \u0022\u003c/td\u003e\u003c/tr\u003e\u0022;\n-\t\t\t\t}\n-\n-\t\t\t\ts +\u003d \u0022\u003c/table\u003e\u003c/td\u003e\u003c/tr\u003e\u0022;\n-\t\t\t\t\n-\t\t\t} // context\n-\t\t\ts \u003d s + \u0022\u003c/table\u003e\u0022;\n-\t\t\t\n-\t\t\tdocument.getElementById(\u0022conninfo\u0022).innerHTML \u003d s;\n-\t\t};\n-\n-\t\tsocket_status.onclose \u003d function(){\n-\t\t\tdocument.getElementById(\u0022title\u0022).innerHTML \u003d \u0022Server Status (Disconnected)\u0022;\n-\t\t\tlws_gray_out(true,{\u0022zindex\u0022:\u0022499\u0022});\n-\t\t};\n-\t} catch(exception) {\n-\t\talert(\u0022\u003cp\u003eError\u0022 + exception); \n-\t}\n-}\n-\n-/* stuff that has to be delayed until all the page assets are loaded */\n-\n-window.addEventListener(\u0022load\u0022, function() {\n-\n-\tlws_gray_out(true,{\u0022zindex\u0022:\u0022499\u0022});\n-\t\n-\tws_open_server_status();\n-\t\n-}, false);\n-\n-}());\n-\n","s":{"c":1756926897,"u": 68670}} ],"g": 190502,"chitpc": 0,"ehitpc": 0,"indexed":0 , "ab": 0, "si": 0, "db":0, "di":0, "sat":0, "lfc": "0000"}