1-) How It Works
This guide describes a simple monitoring solution that periodically collects the JVM metrics of an Apinizer Worker pod and, at the end of the day, produces a single-file interactive HTML report from that data. The solution consists of three parts:- A collector shell script (
monitor.sh) — periodically reads the Worker Diagnostics API for the given environment and appends one JSON record per line to a JSONL file. The same script can be run in parallel for multiple environments using different environment name + Worker URL + Env ID arguments. - A Python report generator (
generate_report.py) — reads the JSONL file for the specified environment; normalizes memory, thread, GC, connection, and health metrics; and produces a single HTML file enriched with statistics, anomaly detection, and Chart.js-based interactive charts. - An orchestrator script (
make-report.sh) — locates the JSONL file based on the given environment name and date, checks the minimum record count, and invokes the report generator.
The flow is as follows: Apinizer Worker (
/apinizer/diagnostics/all) → monitor.sh <environment> (every 2 minutes) → <environment>-worker-jvm-YYYYMMDD.jsonl → make-report.sh <environment> → generate_report.py → apinizer-worker-report-<environment>-YYYYMMDD.html2-) Prerequisites
The following requirements must be met on the machine where you will run the solution:bash,curl,sed,wc(available in a typical Linux environment)python3(3.9 or higher; required for type hints such aslist[dict])- The report generator uses only the standard library; no
pip installis required. Chart.js, which is needed to draw the charts, is embedded into the generated HTML file via a CDN — an internet connection is sufficient when viewing the report. - Network access to the Diagnostics API of the Worker pods (e.g., from outside the cluster via NodePort or Ingress).
Authorization header in Diagnostics API calls.
3-) Diagnostics API
The collector script calls the following endpoint on the Worker:4-) Setting Up the Collector Script
A singlemonitor.sh file takes the environment you want to monitor as an argument. This way, there is no need for separate script files to monitor different environments (e.g., dev, prod, staging); the same script file can be run in parallel in multiple terminals with different arguments.
Ready-to-use copies of the three scripts described in this guide (
monitor.sh, generate_report.py, make-report.sh) are available at the link below:Download script filesIt is sufficient to download the files into the same directory and make them executable with chmod +x monitor.sh make-report.sh.monitor.sh — Dynamic Collector Script
Save the script below asmonitor.sh. When running it, pass the environment name, Worker URL, and Env ID values as command-line arguments.
Making the Script Executable and Starting It
dev-worker-jvm-20260422.jsonl, prod-worker-jvm-20260422.jsonl); the files do not get mixed up.
By default, the script takes a sample every 120 seconds. For more or less frequent sampling, you can pass a value in seconds as the fourth argument (e.g.,
./monitor.sh prod <url> <id> 60). Going below 60 seconds may place unnecessary load on the Worker; going above 300 seconds may miss short-lived spikes.Keeping the Script Running in the Background
Because the monitoring scripts run in awhile true loop, they stop when the terminal session is closed. To keep them running continuously, one of the following methods can be chosen depending on your use case:
tmuxorscreenfor test/development environments — If you start the script in a separate tmux session, it will continue running even if you close your SSH connection. You can later reattach to the session to monitor the output live. It is a quick and simple solution; however, the script does not automatically start back up when the server is rebooted.systemdservice for production environments — This is the standard method for managing background services on Linux. A.serviceunit file is written for each environment, with arguments supplied via the command line, such asExecStart=/path/to/monitor.sh prod <url> <id>. This way, the service starts automatically when the server is rebooted, is restarted automatically if it exits unexpectedly (Restart=always), and its output is captured in the system logs (journalctl). For production use, this is the preferred method in terms of continuity and observability.
Deployment or a scheduled CronJob.
5-) JSONL Output Format
On each sample, the collector injects acollectedAt field at the beginning of the JSON response returned by the Worker and appends it to the output file as a single line. This format is known as JSONL (JSON Lines) — each line is an independent and valid JSON object.
<ENV_NAME>-worker-jvm-YYYYMMDD.jsonl
For example:
dev-worker-jvm-20260422.jsonlprod-worker-jvm-20260422.jsonlstaging-worker-jvm-20260422.jsonl
6-) Report Generator
The report generator reads the JSONL file for the specified environment and produces a single HTML file. The output file is self-contained; it does not require any additional assets, can be sent as an email attachment, or opened from a shared drive.generate_report.py — Report Generator Script
Savegenerate_report.py in the same directory where the collector script produces its output (alongside the JSONL files). The script is written entirely in the Python standard library and requires no additional dependencies. The full source code is included in the appendix at the end of the document; the main functions and working logic are summarized below.
The script operates in four main stages:
THRESHOLDS dictionary according to your own SLAs.
make-report.sh — Orchestrator Script
The script below locates the JSONL file based on the given environment name and date, and invokes the report generator. Save it asmake-report.sh in the same directory as generate_report.py.
CLI Usage
generate_report.py can also be run directly. The supported options are:
apinizer-worker-report-<ENV>-YYYYMMDD.html.
7-) Report Contents
The generated HTML file is a single-page interactive dashboard. The top section displays the environment label, report summary, and anomaly badges; the content is split into six tabs.
Tabs
- Overview — Summary cards for key metrics such as Heap %, Heap Max, thread count, response time, active connections, GC Young rate, GC Old, and deadlocks; below them, time series charts (Heap %, response time, thread count, active connections) comparing all pods on the same graph. This is the tab selected when the page first opens.
- Memory — Charts for heap usage (MB and %) along with non-heap / metaspace charts. Ideal for seeing how heap usage changes over time; in a stable environment, the heap forms a sawtooth pattern, whereas a pod experiencing a memory leak shows a continuously upward-sloping curve.

- Threads — Thread state distribution (RUNNABLE / WAITING / TIMED_WAITING),
apinizer-asyncexecutor pool status (pool size, active, queue), and total started thread count per pod.

- GC — G1 Young and Concurrent GC counters, along with cumulative GC time charts.
- Connections — Leased and available metrics of the HTTP connection pool, along with maintenance pool queue charts.
- Anomalies — List of metrics where threshold violations were detected; shows which metric, in how many samples, and at what peak value the warning or critical level was reached. A per-pod summary table is located at the bottom of the page.

Tab contents work on a “lazy build” principle: charts are only rendered when a tab is clicked. This keeps the initial load fast even if the report contains thousands of data points.
Comparing Multiple Environments
Since the report is now generated for a single environment, when you want to compare two environments, it is sufficient to generate a separate report for each environment and open the HTML files side by side:8-) Troubleshooting
ERROR: 401 Unauthorized — check the ENV_ID value.
The ENV_ID is incorrect, or the ID of a different environment has been entered. Retrieve the correct environment’s ID again from the API Manager → Environments menu.
ERROR: Could not reach Worker ({WORKER_URL})
There is no TCP-level access to the Worker address. Network rules, the Ingress/NodePort definition, or proxy settings should be checked. A quick verification can be made by manually sending a request with curl -v "${WORKER_URL}/apinizer/diagnostics/all?internal=true".
ERROR: ENV_NAME, WORKER_URL, and ENV_ID are required.
monitor.sh was run with no arguments or with missing arguments. If you have not filled in the DEFAULT_* values at the top of the script, you must pass all three arguments on the command line.
ERROR: <env>-worker-jvm-YYYYMMDD.jsonl does not exist. Is monitor.sh <env> ... running?
make-report.sh looks for the JSONL file belonging to the environment you specified. The collector may not have been started yet, may have been run with a different environment name, or may be running in a different directory. The collector script and the report generator must run in the same directory.
WARNING: There should be at least 3 records for the charts to be meaningful.
If the collector has just been started, enough samples may not yet have accumulated. At the default 120-second interval, waiting at least 6 minutes is sufficient for a meaningful report.
Some pods do not appear in the report at all.
If the Worker Deployment is running with multiple replicas, requests coming in through a Service or Ingress are distributed across different pods. The collector may land on a random pod for each request; in short-term monitoring, some pods will have a low chance of being sampled. To monitor per pod, you can run additional collectors that target each pod individually (e.g., via a headless service or pod DNS).
