|
| 1 | +// |
| 2 | +// Licensed to the Apache Software Foundation (ASF) under one or more |
| 3 | +// contributor license agreements. See the NOTICE file distributed with |
| 4 | +// this work for additional information regarding copyright ownership. |
| 5 | +// The ASF licenses this file to You under the Apache License, Version 2.0 |
| 6 | +// (the "License"); you may not use this file except in compliance with |
| 7 | +// the License. You may obtain a copy of the License at |
| 8 | +// |
| 9 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +// |
| 11 | +// Unless required by applicable law or agreed to in writing, software |
| 12 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +// See the License for the specific language governing permissions and |
| 15 | +// limitations under the License. |
| 16 | +// |
| 17 | + |
| 18 | += Monitoring with Prometheus |
| 19 | +:toc: |
| 20 | +:toclevels: 3 |
| 21 | + |
| 22 | +Tika Server can publish operational metrics in the Prometheus text exposition format, |
| 23 | +via https://micrometer.io[Micrometer]. The metrics are built for two questions an operator |
| 24 | +of a parse fleet actually has: _is this server saturated_ (scale on that, not on CPU) and |
| 25 | +_are its forked workers dying_ (alert on that). |
| 26 | + |
| 27 | +== Enabling |
| 28 | + |
| 29 | +Metrics are off by default. Setting a metrics port turns them on; nothing else is needed. |
| 30 | + |
| 31 | +[source,bash] |
| 32 | +---- |
| 33 | +java -jar tika-server-standard-X.Y.Z.jar --metricsPort 9404 |
| 34 | +---- |
| 35 | + |
| 36 | +or in `tika-config.json`: |
| 37 | + |
| 38 | +[source,json] |
| 39 | +---- |
| 40 | +{ |
| 41 | + "server": { |
| 42 | + "port": 9998, |
| 43 | + "metrics": { |
| 44 | + "port": 9404, |
| 45 | + "host": "127.0.0.1", |
| 46 | + "commonTags": { "cluster": "blue" } |
| 47 | + } |
| 48 | + } |
| 49 | +} |
| 50 | +---- |
| 51 | + |
| 52 | +[cols="1,3"] |
| 53 | +|=== |
| 54 | +|Key |Meaning |
| 55 | + |
| 56 | +|`port` |
| 57 | +|The scrape port. Unset means metrics are entirely off: no registry, no meters, no |
| 58 | +listener. `--metricsPort` on the command line overrides the JSON value. Must differ from |
| 59 | +the server port. |
| 60 | + |
| 61 | +|`host` |
| 62 | +|Bind address for the scrape listener. Defaults to the server's own host (`-h`), so a |
| 63 | +server started with `-h 0.0.0.0` in a container exposes its metrics on the pod IP with no |
| 64 | +extra configuration. Set this to `127.0.0.1` to restrict scraping to a sidecar. |
| 65 | + |
| 66 | +|`commonTags` |
| 67 | +|Labels added to every series, for example a cluster or tier name. |
| 68 | +|=== |
| 69 | + |
| 70 | +Metrics are served on a *separate port* from the parse endpoints, on purpose. The parse |
| 71 | +port receives untrusted documents; the metrics port should be reachable only by your |
| 72 | +scraper. Keep them apart at the network layer (a Kubernetes `NetworkPolicy`, a security |
| 73 | +group). The scrape listener answers only `GET /metrics` -- every parse endpoint is `404` |
| 74 | +there, and `/metrics` is `404` on the parse port. It also has its own small thread pool, |
| 75 | +so a scrape never waits behind a slow parse. |
| 76 | + |
| 77 | +The scrape listener is plain HTTP even when the parse port uses |
| 78 | +xref:using-tika/server/tls.adoc[TLS]. |
| 79 | + |
| 80 | +== Probes |
| 81 | + |
| 82 | +Point Kubernetes liveness and readiness probes at the parse port (for example |
| 83 | +`GET /version`, or `GET /status` when that endpoint is enabled), never at `/metrics`. A |
| 84 | +scrape target is not a health check: the listener stays up while the parser is failing, |
| 85 | +and a metrics misconfiguration must not take a healthy parser out of rotation. |
| 86 | + |
| 87 | +== Scrape configuration |
| 88 | + |
| 89 | +[source,yaml] |
| 90 | +---- |
| 91 | +scrape_configs: |
| 92 | + - job_name: tika-server |
| 93 | + static_configs: |
| 94 | + - targets: ['tika-1:9404', 'tika-2:9404'] |
| 95 | +---- |
| 96 | + |
| 97 | +For the Prometheus Operator, a `ServiceMonitor` selecting a Service that exposes the |
| 98 | +metrics port works the same way. |
| 99 | + |
| 100 | +== Meters |
| 101 | + |
| 102 | +Durations are Micrometer timers exported in seconds with a fixed histogram of twelve |
| 103 | +buckets (10ms, 50ms, 100ms, 250ms, 500ms, 1s, 2s, 5s, 10s, 30s, 60s, 120s), so |
| 104 | +`histogram_quantile` works and the series count per pod stays small. Every label value is |
| 105 | +drawn from a fixed set -- endpoint names, status classes, enum names -- never from the |
| 106 | +request, so cardinality cannot grow with traffic. |
| 107 | + |
| 108 | +=== HTTP (parse port) |
| 109 | + |
| 110 | +[cols="2,1,2,3"] |
| 111 | +|=== |
| 112 | +|Meter |Type |Labels |Meaning |
| 113 | + |
| 114 | +|`tika_server_requests_seconds` |
| 115 | +|timer |
| 116 | +|`endpoint`, `method`, `status` |
| 117 | +|Every request on the parse port, timed from before routing to the response. `endpoint` |
| 118 | +is the first path segment when it is one of the server's endpoints (`tika`, `rmeta`, |
| 119 | +`meta`, `unpack`, `detect`, `language`, `mime-types`, `detectors`, `parsers`, `version`, |
| 120 | +`status`, `pipes`, `async`), `other` for any other resource, `unmatched` for a `404` |
| 121 | +that matched nothing. `status` is the class: `2xx`, `4xx`, `5xx`. |
| 122 | + |
| 123 | +|`tika_server_request_size_bytes` |
| 124 | +|summary |
| 125 | +|`endpoint` |
| 126 | +|Request body size from `Content-Length`; chunked uploads have none and are not recorded. |
| 127 | + |
| 128 | +|`tika_server_response_size_bytes` |
| 129 | +|summary |
| 130 | +|`endpoint` |
| 131 | +|Bytes of the response entity, before any compression. |
| 132 | + |
| 133 | +|`tika_server_rejected_total` |
| 134 | +|counter |
| 135 | +|`reason` |
| 136 | +|Requests refused for capacity reasons, by the status the server already uses to encode |
| 137 | +them: `busy_429` (the `/async` queue was full, or no fork was free within |
| 138 | +`maxWaitForClientMillis`), `crash_503` (the fork serving the request OOM'd, timed out or |
| 139 | +crashed), `payload_413` (body over `maxRequestSizeBytes` or the IPC payload limit). |
| 140 | + |
| 141 | +|`tika_server_tasks_active` |
| 142 | +|gauge |
| 143 | +| |
| 144 | +|Parse/detect tasks in flight right now. |
| 145 | + |
| 146 | +|`tika_server_tasks_started_total` |
| 147 | +|counter |
| 148 | +| |
| 149 | +|Tasks started since the server came up. |
| 150 | + |
| 151 | +|`jetty_threads_*` |
| 152 | +|gauges |
| 153 | +| |
| 154 | +|The parse port's request thread pool. `jetty_threads_jobs` (queued jobs) is HTTP-level |
| 155 | +backpressure: requests waiting for a thread. |
| 156 | +|=== |
| 157 | + |
| 158 | +=== Forked workers |
| 159 | + |
| 160 | +[cols="2,1,2,3"] |
| 161 | +|=== |
| 162 | +|Meter |Type |Labels |Meaning |
| 163 | + |
| 164 | +|`tika_pipes_workers` |
| 165 | +|gauge |
| 166 | +|`state` |
| 167 | +|Forked pipes workers `busy` and `idle`. `busy / (busy + idle)` sustained near 1 means the |
| 168 | +server is at capacity; see xref:pipes/cpu-sizing.adoc[Forked-JVM CPU and Heap Sizing] |
| 169 | +before raising `numClients`. |
| 170 | + |
| 171 | +|`tika_pipes_worker_restarts_total` |
| 172 | +|counter |
| 173 | +|`reason` |
| 174 | +|Forked workers restarted, by why: `oom`, `timeout`, `crash` (an unexplained exit), |
| 175 | +`max_files` (routine recycling after `maxFilesProcessedPerProcess`), `idle` (the worker |
| 176 | +shut itself down after `socketTimeoutMillis` without a request -- exit code 24 -- and was |
| 177 | +started again on the next one), `connection_abandoned` (a request was interrupted mid-flight). Alert on |
| 178 | +`oom`, `timeout` and `crash`; `max_files` and `idle` are expected. |
| 179 | + |
| 180 | +|`tika_pipes_queue_depth` |
| 181 | +|gauge |
| 182 | +| |
| 183 | +|Tuples accepted by `/async` and not yet picked up by a worker. Only present when the |
| 184 | +`async` endpoint is enabled. |
| 185 | +|=== |
| 186 | + |
| 187 | +=== JVM and process |
| 188 | + |
| 189 | +The standard Micrometer binders are enabled: `jvm_memory_*`, `jvm_gc_*`, `jvm_threads_*`, |
| 190 | +`process_cpu_usage`, `system_cpu_usage`, `process_files_*`, `process_uptime_seconds`. |
| 191 | +These describe the server JVM only, not the forked workers. |
| 192 | + |
| 193 | +== What to scale and alert on |
| 194 | + |
| 195 | +* Saturation, for an autoscaler: `tika_pipes_workers{state="busy"}` as a ratio of the |
| 196 | + total, the rate of `tika_server_rejected_total{reason="busy_429"}`, and |
| 197 | + `jetty_threads_jobs`. These move before latency does, which CPU does not. |
| 198 | +* Failure, for alerting: `rate(tika_pipes_worker_restarts_total{reason=~"oom|timeout|crash"}[5m])` |
| 199 | + and `tika_server_rejected_total{reason="crash_503"}`. A `503` tells the client the |
| 200 | + document broke a fork; the restart counter tells you how often that is happening. |
| 201 | +* Latency: `histogram_quantile(0.95, sum by (le, endpoint) (rate(tika_server_requests_seconds_bucket[5m])))`. |
| 202 | + |
| 203 | +== Not counted |
| 204 | + |
| 205 | +An exception that no JAX-RS `ExceptionMapper` handles is answered by the servlet |
| 206 | +container's own error path, which bypasses the response filter that records |
| 207 | +`tika_server_requests_seconds`. Tika Server maps its own parse and pipes failures, so this |
| 208 | +only affects genuine server bugs. |
0 commit comments