chronos-ruby 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3fc9daff111666fdf7309797c5f8aa81a639c8f59ea1733be48740b9aea6b247
4
- data.tar.gz: a8d7cd786b233078e5516e3d1ce0c92b986d045ebd4d8c29a7d9cd80ae75fb93
3
+ metadata.gz: 0127def72275a2d4c08314040c04e34a9606fed13982d84b06c465dee7be4cf6
4
+ data.tar.gz: 99f272314517e6a525223ce86964b70fcdfce717eb0e434b9c77e275031913b7
5
5
  SHA512:
6
- metadata.gz: 1f60a09480aa5766676d673579ffe1b5b007d044881fc9b1f70f672c414ddf7e1a014cc63eaeee8ee6f63fa7261aa5dc92f8f9b1cc6089898ecf015d1940a83c
7
- data.tar.gz: '08342c5a665004cbfa9fec52164f86e82d003b3d587bf931ae220c44ec4188c91141852e424f726f55a7b7d5dec4592190dc7cef9dc6c4e05f0eec8255bd32b4'
6
+ metadata.gz: 93b37af1a5b51284a6c6de1c19e12de18ac23b04bfa4a04f33b7fce5cb611ec6f940d4ec2508c8a2b8d72f91e9a2a9a812a3b48d72f60dfefd642fb7cf3fe138
7
+ data.tar.gz: 4c653acb6d7aa90d534a7d496568fca64fb9ba9bce9039c6ca68fad71c1b5aef3f4e5de541a3f3d0f6064f08653359bcc2afc30e3a2d82c910942f0c1e103d4a
data/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes are documented here. The project follows Semantic Versioning
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.1.0] - 2026-08-05
8
+
9
+ ### Added
10
+
11
+ - bounded normalized SELECT analysis with access columns, index candidates, existing-index comparison, optional table statistics, and allowlisted non-executing query plans;
12
+ - structured query diagnostics and severity counts for errors, warnings, information, and suggestions, including actionable N+1 and index guidance;
13
+ - approximate p50/p95/p99 metrics, complete outer-transaction timing, expanded adapter error families, and trace/fingerprint loss counters;
14
+ - explicit low-risk defaults and bounded opt-in configuration for database index/statistics/plan inspection.
15
+
16
+ ### Changed
17
+
18
+ - aggregate drains preserve active trace trackers until request completion or idle TTL instead of discarding incomplete correlation;
19
+ - possible N+1 now requires a non-cached SELECT, and query analysis sent to consumers prefers inspected evidence over an earlier static-only observation.
20
+
21
+ ### Security
22
+
23
+ - query inspection never uses `EXPLAIN ANALYZE`, never executes DDL, omits raw SQL/binds/predicates/messages, and retains only bounded schema and planner fields.
24
+
7
25
  ## [1.0.0] - 2026-07-29
8
26
 
9
27
  ### Added
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Chronos Ruby
2
2
 
3
- Chronos Ruby 1.0.0 é o agente independente de framework para enviar exceções e telemetria limitada de aplicações Ruby ao Chronos. Esta é a linha estável legado, compatível com o protocolo v1 e voltada a Ruby 2.2.10–2.6.
3
+ Chronos Ruby 1.1.0 é o agente independente de framework para enviar exceções e telemetria limitada de aplicações Ruby ao Chronos. Esta é a linha estável legado, compatível com o protocolo v1 e voltada a Ruby 2.2.10–2.6.
4
4
 
5
5
  ## O que a gem coleta
6
6
 
7
- A versão 1.0 pode coletar:
7
+ A versão 1.1 pode coletar:
8
8
 
9
9
  - classe, mensagem, backtrace estruturado e causas encadeadas da exceção;
10
10
  - timestamp, severidade, tags e fingerprint opcional;
@@ -20,11 +20,11 @@ Veja a tabela completa em [Dados coletados](docs/data-collected.md).
20
20
 
21
21
  ## O que não é coletado por padrão
22
22
 
23
- A gem não varre variáveis de ambiente, sistema de arquivos ou lockfiles e não bodies HTTP, cookies, headers de autorização, conteúdo de e-mail, SQL bruto, binds, valores de cache ou código-fonte. O inventário de dependências contém somente nomes e versões já carregados, uma vez por agente. A aplicação continua responsável por minimização e base legal dos dados enviados.
23
+ A gem não varre variáveis de ambiente, sistema de arquivos ou lockfiles e não coleta bodies HTTP, cookies, headers de autorização, conteúdo de e-mail, SQL bruto, binds, valores de cache ou código-fonte. A inspeção de plano, desativada por padrão, usa o SQL original somente na conexão local para `EXPLAIN` sem `ANALYZE` e o descarta. O inventário de dependências contém somente nomes e versões já carregados, uma vez por agente. A aplicação continua responsável por minimização e base legal dos dados enviados.
24
24
 
25
25
  ## Versões Ruby e Rails suportadas
26
26
 
27
- A versão 1.0.0 suporta Ruby puro e Rack em Ruby 2.2.10, 2.3.8, 2.4.10, 2.5.9 e 2.6.10. As combinações Rails validadas são Rails 4.2 com Ruby 2.2.10/2.3.8 e Rails 5.2 com Ruby 2.5.9/2.6.10. Sidekiq 4.2.10 com Ruby 2.2.10 e Sidekiq 5.2.10 com Ruby 2.5.9 também possuem gates dedicados.
27
+ A versão 1.1.0 suporta Ruby puro e Rack em Ruby 2.2.10, 2.3.8, 2.4.10, 2.5.9 e 2.6.10. As combinações Rails validadas são Rails 4.2 com Ruby 2.2.10/2.3.8 e Rails 5.2 com Ruby 2.5.9/2.6.10. Sidekiq 4.2.10 com Ruby 2.2.10 e Sidekiq 5.2.10 com Ruby 2.5.9 também possuem gates dedicados. Ruby 2.7/Rails 6 não é declarado nesta release porque ainda não possui aplicação e matriz completas.
28
28
 
29
29
  Rubies e frameworks antigos estão fora do suporte de segurança de seus mantenedores. A Chronos oferece compatibilidade técnica, não manutenção de segurança do runtime. Veja [Compatibilidade](docs/compatibility.md).
30
30
 
@@ -33,7 +33,7 @@ Rubies e frameworks antigos estão fora do suporte de segurança de seus mantene
33
33
  Obrigatório: adicione a versão estável ao `Gemfile`.
34
34
 
35
35
  ```ruby
36
- gem "chronos-ruby", "~> 1.0.0"
36
+ gem "chronos-ruby", "~> 1.1.0"
37
37
  ```
38
38
 
39
39
  Em runtimes antigos, use Bundler compatível:
@@ -46,7 +46,7 @@ bundle _1.17.3_ install
46
46
  Sem Bundler:
47
47
 
48
48
  ```bash
49
- gem install chronos-ruby -v 1.0.0
49
+ gem install chronos-ruby -v 1.1.0
50
50
  ```
51
51
 
52
52
  ## Instalação em Rails
@@ -54,7 +54,7 @@ gem install chronos-ruby -v 1.0.0
54
54
  Obrigatório: carregue a integração Rails explicitamente para manter Rails/ActiveSupport fora de aplicações Ruby puras.
55
55
 
56
56
  ```ruby
57
- gem "chronos-ruby", "~> 1.0.0", :require => "chronos/rails"
57
+ gem "chronos-ruby", "~> 1.1.0", :require => "chronos/rails"
58
58
  ```
59
59
 
60
60
  Gere o initializer:
@@ -174,7 +174,7 @@ A regra recebe um notice normalizado e imutável, e somente `true` descarta. Fal
174
174
 
175
175
  ## Monitoramento de performance
176
176
 
177
- A Versão 0.7 introduziu agregação local de requests, queries e jobs em `metric_batch`; a Versão 0.8 adicionou HTTP externo. Grupos possuem contagem, erro, duração, histograma, status e breakdown limitados. Percentis são calculados no SaaS.
177
+ A Versão 0.7 introduziu agregação local de requests, queries e jobs em `metric_batch`; a Versão 0.8 adicionou HTTP externo. Grupos possuem contagem, erro, duração, histograma, percentis aproximados, severidades, diagnósticos, status e breakdown limitados.
178
178
 
179
179
  ```ruby
180
180
  Chronos.configure do |config|
@@ -185,10 +185,22 @@ Chronos.configure do |config|
185
185
  config.apm_max_queries_per_request = 100
186
186
  config.apm_slow_query_threshold_ms = 500.0
187
187
  config.apm_n_plus_one_threshold = 5
188
+ config.apm_trace_ttl_seconds = 60.0
189
+ config.apm_query_analysis_enabled = true
190
+ config.apm_query_analysis_max_queries = 100
191
+
192
+ # Opt-in: cada fingerprint elegível pode consultar catálogo/estatística/plano.
193
+ config.apm_query_inspection_enabled = false
194
+ config.apm_query_statistics_enabled = false
195
+ config.apm_query_plan_enabled = false
196
+ config.apm_query_inspection_min_duration_ms = 500.0
197
+ config.apm_query_inspection_max_queries = 20
198
+ config.apm_transaction_tracking_enabled = true
199
+ config.apm_transaction_max_connections = 100
188
200
  end
189
201
  ```
190
202
 
191
- SQL bruto e binds não são lidos. Sinais de query lenta, repetição, possível N+1, transação longa, conexão e deadlock são heurísticos. Veja [APM](docs/modules/apm-aggregation.md), [Requests](docs/modules/request-monitoring.md) e [SQL](docs/modules/sql-monitoring.md).
203
+ Por padrão, SQL bruto e binds não são lidos pelo pipeline de análise. A inspeção opt-in usa o SQL original apenas localmente para solicitar `EXPLAIN` sem `ANALYZE`; nunca o inclui no evento. A análise estática produz candidatos, não ordens de criação de índice. Erros usam severidade `error`; lentidão e risco usam `warning`; padrões observados usam `info`; correções propostas usam `suggestion`. Veja [APM](docs/modules/apm-aggregation.md), [Requests](docs/modules/request-monitoring.md) e [SQL](docs/modules/sql-monitoring.md).
192
204
 
193
205
  ## Sidekiq e Active Job
194
206
 
@@ -196,7 +208,7 @@ A versão `0.6.0.pre.1` introduziu middleware Sidekiq 4/5; a API estável manté
196
208
 
197
209
  ```ruby
198
210
  gem "sidekiq", "~> 5.0"
199
- gem "chronos-ruby", "~> 1.0.0", :require => "chronos/sidekiq"
211
+ gem "chronos-ruby", "~> 1.1.0", :require => "chronos/sidekiq"
200
212
  ```
201
213
 
202
214
  O envelope de contexto não altera argumentos públicos e contém somente IDs limitados de trace/request. Active Job usa um campo serializado com namespace (`chronos_context`) e hooks públicos. Erros aninhados são deduplicados e reerguidos. Veja [Sidekiq legado](docs/modules/sidekiq-legacy.md), [Active Job](docs/modules/active-job.md) e [Jobs](docs/modules/job-monitoring.md).
@@ -24,10 +24,60 @@
24
24
  "type": "object",
25
25
  "required": ["total", "min", "max", "average"]
26
26
  },
27
+ "percentiles_ms": {
28
+ "type": "object",
29
+ "additionalProperties": false,
30
+ "required": ["p50", "p95", "p99"],
31
+ "properties": {
32
+ "p50": {"type": "number", "minimum": 0},
33
+ "p95": {"type": "number", "minimum": 0},
34
+ "p99": {"type": "number", "minimum": 0}
35
+ }
36
+ },
27
37
  "histogram": {"type": "array", "maxItems": 20},
28
38
  "breakdown_ms": {"type": "object"},
29
39
  "signals": {"type": "object"},
30
- "status_codes": {"type": "object"}
40
+ "status_codes": {"type": "object"},
41
+ "severity_counts": {
42
+ "type": "object",
43
+ "additionalProperties": false,
44
+ "properties": {
45
+ "error": {"type": "integer", "minimum": 0},
46
+ "warning": {"type": "integer", "minimum": 0},
47
+ "info": {"type": "integer", "minimum": 0},
48
+ "suggestion": {"type": "integer", "minimum": 0}
49
+ }
50
+ },
51
+ "diagnostics": {
52
+ "type": "array",
53
+ "maxItems": 20,
54
+ "items": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["code", "severity", "category", "message", "evidence", "count"],
58
+ "properties": {
59
+ "code": {"type": "string", "maxLength": 128},
60
+ "severity": {"enum": ["error", "warning", "info", "suggestion"]},
61
+ "category": {"type": "string", "maxLength": 64},
62
+ "message": {"type": "string", "maxLength": 512},
63
+ "recommendation": {"type": "string", "maxLength": 512},
64
+ "evidence": {"type": "object"},
65
+ "count": {"type": "integer", "minimum": 1}
66
+ }
67
+ }
68
+ },
69
+ "query_analysis": {"type": "object"},
70
+ "tracking": {
71
+ "type": "object",
72
+ "additionalProperties": false,
73
+ "required": ["active_traces", "dropped_trace_trackers", "expired_trace_trackers", "dropped_query_fingerprints"],
74
+ "properties": {
75
+ "active_traces": {"type": "integer", "minimum": 0},
76
+ "dropped_trace_trackers": {"type": "integer", "minimum": 0},
77
+ "expired_trace_trackers": {"type": "integer", "minimum": 0},
78
+ "dropped_query_fingerprints": {"type": "integer", "minimum": 0}
79
+ }
80
+ }
31
81
  }
32
82
  }
33
83
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Accepted for version 0.7.
5
+ Accepted for version 0.7; trace-drain behavior amended by ADR-019.
6
6
 
7
7
  ## Context
8
8
 
@@ -12,7 +12,7 @@ Sending every request, SQL query, and job as an independent event increases netw
12
12
 
13
13
  Aggregate request, query, and job observations by bounded low-cardinality dimensions. Store only counters, error counters, duration sum/min/max, fixed histogram buckets, component totals, status counters, and heuristic signal counters. Calculate averages locally and percentiles in the SaaS.
14
14
 
15
- Use a fixed group limit, fixed query-fingerprint limit per trace, fixed batch size, and no new timer thread. Drain on observation threshold, explicit flush, and close. Clear incomplete trace trackers during drain. Normalize SQL comments and literal values before fingerprinting and never read binds. Use a `metric_batch` event through the existing sanitization, queue, retry, circuit breaker, and backlog pipeline.
15
+ Use a fixed group limit, fixed query-fingerprint limit per trace, fixed batch size, and no new timer thread. Drain on observation threshold, explicit flush, and close. ADR-019 later preserves incomplete trace trackers across drains under capacity and idle-TTL limits. Normalize SQL comments and literal values before fingerprinting and never read binds. Use a `metric_batch` event through the existing sanitization, queue, retry, circuit breaker, and backlog pipeline.
16
16
 
17
17
  ## Alternatives
18
18
 
@@ -24,4 +24,4 @@ Delivery volume is reduced, memory remains bounded, request breakdown is availab
24
24
 
25
25
  ## Negative consequences
26
26
 
27
- Process crashes can lose undrained aggregates, incomplete trackers are discarded on drain, local signals are heuristic, and a defensive SQL normalizer cannot understand every database dialect.
27
+ Process crashes can lose undrained aggregates, idle/over-capacity trackers can be discarded and are counted, local signals are heuristic, and a defensive SQL normalizer cannot understand every database dialect.
@@ -0,0 +1,44 @@
1
+ # ADR-019 — Bounded query diagnostics and opt-in database inspection
2
+
3
+ ## Status
4
+
5
+ Accepted.
6
+
7
+ ## Context
8
+
9
+ Fingerprint repetition and duration identify useful SQL symptoms but do not tell the consumer which access columns were observed, whether an existing index covers them, or what the database planner estimated. Running deep analysis inside a legacy application can add latency, expose data, increase cardinality, and violate the fixed-memory requirement.
10
+
11
+ ## Decision
12
+
13
+ Analyze normalized SELECT text by default with a dependency-free bounded heuristic. Emit structured diagnostics with stable code, severity (`error`, `warning`, `info`, `suggestion`), category, bounded evidence, count, and optional recommendation.
14
+
15
+ Define `Ports::QueryInspector` and keep its ActiveRecord implementation in `Chronos::Rails`. Index, statistics, and plan inspection are independent explicit opt-ins. Inspect each eligible fingerprint at most once per subscriber, cap inspected fingerprints, and require a duration threshold. Plans use `EXPLAIN` only, never `EXPLAIN ANALYZE`; DDL and automatic index creation are prohibited. Retain only allowlisted schema/planner fields and exception class names. Never retain raw SQL, binds, plan predicates, arbitrary rows, or exception messages.
16
+
17
+ Preserve trace trackers across metric drains with capacity and idle TTL. Report loss counters. Measure outer transaction time with bounded per-connection notification state. Derive approximate p50/p95/p99 from fixed histograms without retaining duration samples.
18
+
19
+ Keep new metric fields optional in the v1 JSON Schema for backward compatibility, although the new agent emits them. Consumer models use diagnostic code/severity as stable fields and treat message text as a fallback presentation value.
20
+
21
+ ## Alternatives
22
+
23
+ - Server-only static analysis was rejected because existing-index comparison requires local schema evidence and immediate signals remain useful.
24
+ - Automatic `EXPLAIN ANALYZE` was rejected because it executes the query.
25
+ - Automatic `CREATE INDEX` was rejected because it can block writes, consume storage, and regress mutation workloads.
26
+ - Shipping raw SQL/plans was rejected for privacy and cardinality.
27
+ - A full SQL-parser dependency was rejected for legacy compatibility, installation cost, and dialect complexity.
28
+ - Unbounded trace/inspection caches were rejected for memory safety.
29
+
30
+ ## Positive consequences
31
+
32
+ - Consumers receive actionable, typed evidence with clear confidence limits.
33
+ - Default analysis adds no database round trip.
34
+ - Opt-in inspection is read-only, sampled by fingerprint/duration, and contains adapter failures.
35
+ - Errors, warnings, information, and suggestions can map directly to observer models.
36
+ - Trace correlation no longer disappears at every aggregate flush.
37
+
38
+ ## Negative consequences
39
+
40
+ - Static parsing can miss complex SQL, aliases, expressions, partial indexes, and dialect features.
41
+ - Even non-executing `EXPLAIN` and catalog reads consume a connection/planner budget and have no portable legacy timeout.
42
+ - Index recommendations do not know true selectivity, write cost, storage, or production distribution.
43
+ - Approximate percentiles use histogram upper bounds and may be coarse.
44
+ - Transaction elapsed time is measured between notification callbacks, not database wire boundaries.
data/docs/architecture.md CHANGED
@@ -20,7 +20,10 @@ flowchart TB
20
20
  Sidekiq --> Envelope[Versioned job-envelope context]
21
21
  Application --> Telemetry[Core / TelemetryEvent]
22
22
  Telemetry --> APM[Application / ApmAggregator]
23
- APM --> Metrics[Core / MetricAggregate and SqlNormalizer]
23
+ APM --> Metrics[Core / MetricAggregate, SqlNormalizer, and SqlQueryAnalyzer]
24
+ Rails --> Inspector[ActiveRecordQueryInspector opt-in]
25
+ Inspector --> QueryPort[Ports / QueryInspector]
26
+ Inspector --> Metrics
24
27
  NetHTTP[Explicit Net::HTTP instance wrapper] --> Facade
25
28
  Rails --> Cache[Core / CacheNormalizer]
26
29
  Application --> Dependencies[Application / DependencyReporter]
@@ -53,7 +56,7 @@ Rails timings become immutable `TelemetryEvent` values. `CaptureTelemetry` appli
53
56
 
54
57
  Sidekiq client middleware writes a versioned, allowlisted trace/request context beside the job's public arguments. Server middleware restores a job scope, limits arguments and tags, emits a job observation, and routes a failure through the existing notice pipeline before re-raising it. Sidekiq retains queue, retry, thread, and connection lifecycle ownership.
55
58
 
56
- Version 0.7 routes request, query, and job observations through `ApmAggregator`. `SqlNormalizer` removes values before grouping, while `MetricAggregate` owns fixed numerical statistics. Aggregates and per-trace query trackers are bounded and mutex-protected. Threshold or lifecycle drains create `metric_batch` telemetry that passes through the existing sanitizer and delivery pipeline. No APM-specific thread is created.
59
+ Version 0.7 routes request, query, and job observations through `ApmAggregator`. `SqlNormalizer` removes values before grouping, `SqlQueryAnalyzer` derives bounded access evidence, and `MetricAggregate` owns fixed numerical statistics, approximate percentiles, severities, diagnostics, and one representative analysis. Active trace trackers survive group drains, expire by TTL, and expose loss counters. `ActiveRecordQueryInspector` is an optional Rails adapter behind `Ports::QueryInspector`; it reads public index metadata and allowlisted PostgreSQL/MySQL statistics/plans without `ANALYZE` or DDL. Threshold or lifecycle drains create `metric_batch` telemetry that passes through the existing sanitizer and delivery pipeline. No APM-specific thread is created.
57
60
 
58
61
  Version 0.8 prepends a wrapper only to each explicitly selected `Net::HTTP` object. The wrapper records bounded outcome metadata and feeds the existing APM aggregator; it does not modify `Net::HTTP` globally. `CacheNormalizer` turns public Rails cache notifications into bounded metadata before delivery. `DependencyReporter` reads already loaded runtime metadata once per agent under a mutex and queues it as a separate event.
59
62
 
@@ -1,6 +1,6 @@
1
1
  # Compatibility
2
2
 
3
- Chronos Ruby 1.0 is the stable legacy line. Technical compatibility does not make an end-of-life Ruby, Rails, Rack, or Sidekiq release secure.
3
+ Chronos Ruby 1.1 is the current stable legacy line. Technical compatibility does not make an end-of-life Ruby, Rails, Rack, or Sidekiq release secure. The planned Ruby 2.7/Rails 6 transitional matrix remains deferred until it has dedicated CI and a real application gate.
4
4
 
5
5
  ## Core and Rack
6
6
 
@@ -33,11 +33,11 @@ Chronos Ruby 1.0 is the stable legacy line. Technical compatibility does not mak
33
33
 
34
34
  Active Job uses the public `serialize`, `deserialize`, and `perform_now` extension points with a bounded namespaced field. Support follows the validated Rails pairs above. Adapters that bypass these hooks require their own evidence.
35
35
 
36
- The release workflow repeats every supported pair before publishing. The green candidate evidence that permitted the 1.0 promotion is recorded in [Version 1.0 readiness](release-1.0-readiness.md).
36
+ The release workflow repeats every supported pair before publishing. Historical 1.0 evidence remains in [Version 1.0 readiness](release-1.0-readiness.md); the 1.1 candidate is recorded in [Version 1.1 readiness](release-1.1-readiness.md).
37
37
 
38
38
  Status meanings:
39
39
 
40
40
  - Supported: every mandatory compatibility gate for the exact pair passes.
41
- - Best effort: intended to work but missing a complete gate; no 1.0 pair is advertised this way.
41
+ - Best effort: intended to work but missing a complete gate; no 1.1 pair is advertised this way.
42
42
  - Deprecated: still tested while removal is planned.
43
- - Unsupported: outside the tested 1.0 contract.
43
+ - Unsupported: outside the tested 1.1 contract.
@@ -63,6 +63,16 @@
63
63
  | `apm_long_transaction_threshold_ms` | Optional | `1000.0` | Transaction-labelled SQL duration that produces a signal |
64
64
  | `apm_n_plus_one_threshold` | Optional | `5` | Repeated fingerprint count producing one possible-N+1 signal; minimum 2 |
65
65
  | `apm_histogram_buckets` | Optional | Fixed millisecond boundaries | Increasing positive duration boundaries; at most 19 plus `+Inf` |
66
+ | `apm_trace_ttl_seconds` | Optional | `60.0` | Maximum idle time retained for an incomplete trace or transaction tracker |
67
+ | `apm_query_analysis_enabled` | Optional | `true` | Analyzes bounded normalized SELECT shapes and emits informational/index-candidate diagnostics |
68
+ | `apm_query_analysis_max_queries` | Optional | `100` | Maximum normalized fingerprints analyzed and cached per subscriber; range 1–500 |
69
+ | `apm_query_inspection_enabled` | Optional | `false` | Opts in to read-only comparison with ActiveRecord index metadata |
70
+ | `apm_query_statistics_enabled` | Optional | `false` | Adds adapter-specific estimated table-row statistics; requires query inspection |
71
+ | `apm_query_plan_enabled` | Optional | `false` | Adds allowlisted `EXPLAIN` evidence for eligible SELECTs; never uses `ANALYZE` |
72
+ | `apm_query_inspection_min_duration_ms` | Optional | `500.0` | Minimum observed query duration before an opt-in database inspection |
73
+ | `apm_query_inspection_max_queries` | Optional | `20` | Maximum unique fingerprints inspected per subscriber; range 1–100 |
74
+ | `apm_transaction_tracking_enabled` | Optional | `true` | Measures bounded outer transaction elapsed time by notification connection |
75
+ | `apm_transaction_max_connections` | Optional | `100` | Maximum concurrently tracked transaction connections; range 1–500 |
66
76
  | `external_http_enabled` | Optional | `false` | Allows explicit per-instance outbound `Net::HTTP` instrumentation |
67
77
  | `external_http_trace_headers` | Optional | `true` | Propagates Chronos trace/request headers on instrumented requests |
68
78
  | `cache_key_mode` | Optional | `:none` | `:none` omits keys; `:sha256` emits a project-scoped key hash |
@@ -102,6 +112,17 @@ Chronos.configure do |config|
102
112
  config.apm_slow_query_threshold_ms = 500.0
103
113
  config.apm_long_transaction_threshold_ms = 1000.0
104
114
  config.apm_n_plus_one_threshold = 5
115
+ config.apm_trace_ttl_seconds = 60.0
116
+ config.apm_query_analysis_enabled = true
117
+ config.apm_query_analysis_max_queries = 100
118
+ # Database access is explicit because each inspected fingerprint adds read-only work.
119
+ config.apm_query_inspection_enabled = false
120
+ config.apm_query_statistics_enabled = false
121
+ config.apm_query_plan_enabled = false
122
+ config.apm_query_inspection_min_duration_ms = 500.0
123
+ config.apm_query_inspection_max_queries = 20
124
+ config.apm_transaction_tracking_enabled = true
125
+ config.apm_transaction_max_connections = 100
105
126
  config.external_http_enabled = false
106
127
  config.external_http_trace_headers = true
107
128
  config.cache_key_mode = :none
@@ -33,13 +33,19 @@ Version 0.9 emits exceptions, cache telemetry, dependency/deploy events, and agg
33
33
  | Sidekiq arguments | Collected, sanitized, and bounded | First 20 job arguments; collections/depth/strings limited |
34
34
  | Sidekiq tags | Collected and bounded | Job payload or public worker options |
35
35
  | Sidekiq trace/request IDs | Propagated when present; trace generated otherwise | Chronos job-envelope metadata |
36
- | APM counts, error counts/rates, duration total/min/max/average | Aggregated by default | Request, query, and job observations |
37
- | Fixed duration histogram and status counts | Aggregated by default | Local bounded counters |
36
+ | APM counts, error counts/rates, duration total/min/max/average and approximate p50/p95/p99 | Aggregated by default | Request, query, and job observations plus fixed histogram |
37
+ | Fixed duration histogram, status counts, severity counts and bounded diagnostics | Aggregated by default | Local bounded counters and classifiers |
38
38
  | Component breakdown | database/view/external_http/cache/queue/application when observable | Trace-local bounded totals |
39
39
  | Normalized SQL and SHA-256 fingerprint | Collected without comments, literals, or binds | `sql.active_record` payload |
40
40
  | SQL adapter, operation, inferred table, AR name, cache flag, role/shard | Collected when exposed | Public notification payload and connection feature detection |
41
41
  | Slow SQL source frame | Collected only for threshold-selected slow queries | Bounded application call frame |
42
42
  | APM diagnostic signals | Heuristic counters | Local threshold and repetition detection |
43
+ | SELECT tables and equality/range/join/order columns; index candidates | Collected by default from normalized SQL; bounded | Local static query analyzer |
44
+ | Existing index names/columns/uniqueness | Disabled by default; read-only opt-in | Public ActiveRecord schema metadata |
45
+ | Estimated table rows | Disabled by default; read-only opt-in | PostgreSQL `pg_class` or MySQL `information_schema` |
46
+ | Allowlisted plan node type/table/index/estimated rows/cost | Disabled by default; `EXPLAIN` without `ANALYZE` | Local database planner |
47
+ | Complete outer transaction approximate duration | Collected by default with bounded state | BEGIN/COMMIT/ROLLBACK notifications keyed by local connection identity |
48
+ | Trace tracking loss/expiration counters | Aggregated by default | Local bounded tracker lifecycle |
43
49
  | External HTTP host, method, status, duration, timeout, connection-error flag, error class | Disabled by default; per-instance opt-in | Instrumented `Net::HTTP` object |
44
50
  | Chronos trace/request headers | Propagated when available | Current execution context |
45
51
  | Cache operation, backend, namespace, hit/miss, duration | Collected; key/value omitted | ActiveSupport cache notifications |
@@ -50,7 +56,7 @@ Version 0.9 emits exceptions, cache telemetry, dependency/deploy events, and agg
50
56
  | Deploy environment, revision, version, repository, actor, deploy ID, service, region, instance | Explicit synchronous deploy API | Application/deployment integration arguments |
51
57
  | Integration verification ID, fixed test marker, synthetic exception class/message, and receipt correlation | Only when `Chronos.verify_integration` or its Rake task is invoked | Locally generated bounded values |
52
58
 
53
- The gem never collects request bodies, response bodies, raw query strings, cookies, HTTP authorization headers, environment variables in bulk, Git state, source code, raw SQL, SQL bind values, database rows, raw cache keys/values, mail bodies/recipients, Active Job arguments, gem paths, or lockfile contents. Active Job IDs, Sidekiq JIDs/arguments, loaded gem names/versions, and deploy fields are documented integration data.
59
+ The gem never transmits request bodies, response bodies, raw query strings, cookies, HTTP authorization headers, environment variables in bulk, Git state, source code, raw SQL, SQL bind values, database rows, plan predicates, raw cache keys/values, mail bodies/recipients, Active Job arguments, gem paths, or lockfile contents. Opt-in plan inspection temporarily supplies the already observed local SELECT to `EXPLAIN` without `ANALYZE` and discards it. Active Job IDs, Sidekiq JIDs/arguments, loaded gem names/versions, and deploy fields are documented integration data.
54
60
 
55
61
  APM dimensions never include user ID, job ID, raw URL, exception message, bind value, or cache key. Normalized routes replace common numeric/UUID segments. Normalized SQL can retain schema, table, and column identifiers; review those identifiers as part of the privacy audit.
56
62
 
@@ -29,6 +29,12 @@ For network-free per-instance outbound HTTP instrumentation, run:
29
29
  bundle _1.17.3_ exec ruby examples/plain-ruby/external_http.rb
30
30
  ```
31
31
 
32
+ For a network-free normalized-query/index diagnostic example, run:
33
+
34
+ ```bash
35
+ bundle _1.17.3_ exec ruby examples/plain-ruby/query_analysis.rb
36
+ ```
37
+
32
38
  For a network-free deploy event with complete release correlation, run:
33
39
 
34
40
  ```bash
@@ -4,13 +4,13 @@ Version `0.8.0.pre.1` aggregates request, SQL, job, and explicitly instrumented
4
4
 
5
5
  ## Request metrics
6
6
 
7
- Request groups use normalized route and HTTP method. Status codes are counted inside the group so error rate can be calculated without making status part of an unbounded key. Each metric contains count, error count/rate, total, minimum, maximum, average, fixed histogram buckets, and component breakdown. Percentiles remain server-side because retaining every duration locally would violate the memory boundary.
7
+ Request groups use normalized route and HTTP method. Status codes are counted inside the group so error rate can be calculated without making status part of an unbounded key. Each metric contains count, error count/rate, total, minimum, maximum, average, fixed histogram buckets, approximate p50/p95/p99 upper bounds, severity counts, bounded diagnostics, and component breakdown. The SaaS may calculate more precise percentiles from merged histograms; the agent never retains raw duration arrays.
8
8
 
9
9
  Rack records request metrics for non-Rails applications. Rails controller notifications and Rack share `record_event_once`, so the same request is counted only once. Controller notification data wins when available because its route is more precise.
10
10
 
11
11
  ## SQL metrics and signals
12
12
 
13
- `Chronos::Core::SqlNormalizer` removes block/line comments, quoted literal values, numeric values, booleans, nulls, and repeated `IN` values before producing a SHA-256 fingerprint. Binds are never read. Query dimensions can contain adapter, operation, inferred table, bounded normalized query, fingerprint, Active Record operation name, cache flag, connection role/shard, and a bounded source frame for slow sampled queries.
13
+ `Chronos::Core::SqlNormalizer` removes block/line comments, quoted literal values, numeric values, booleans, nulls, and repeated `IN` values before producing a SHA-256 fingerprint. Binds are never read. `SqlQueryAnalyzer` extracts bounded SELECT access columns. The optional `ActiveRecordQueryInspector` compares existing indexes and adds allowlisted statistics/plans without retaining raw SQL. Query dimensions can contain adapter, operation, inferred table, bounded normalized query, fingerprint, Active Record operation name, cache flag, connection role/shard, and a bounded source frame for slow sampled queries.
14
14
 
15
15
  Local signals are intentionally heuristic:
16
16
 
@@ -18,7 +18,8 @@ Local signals are intentionally heuristic:
18
18
  - `repeated_query` after the same fingerprint appears again in one trace;
19
19
  - `possible_n_plus_one` once when the configured repetition threshold is reached;
20
20
  - `long_transaction` for transaction-labelled SQL over its threshold;
21
- - `connection_error` and `deadlock` from bounded exception class names.
21
+ - `connection_error`, query/pool/lock timeout, constraint violation, and `deadlock` from bounded exception class names;
22
+ - index candidate/coverage and sequential-scan evidence from bounded query analysis.
22
23
 
23
24
  The SaaS must confirm and correlate these signals. They are not proof of an N+1, deadlock, or application defect.
24
25
 
@@ -29,9 +30,11 @@ The SaaS must confirm and correlate these signals. They are not proof of an N+1,
29
30
  - at most `apm_max_queries_per_request` fingerprints per trace;
30
31
  - at most 19 configured histogram boundaries plus `+Inf`;
31
32
  - at most `apm_batch_size` groups per payload, with a hard maximum of 50;
32
- - trackers are removed when their request completes and all remaining trackers are cleared on aggregate drain.
33
+ - trackers are removed when their request completes or their idle TTL expires; aggregate drain preserves active trackers;
34
+ - at most 20 diagnostics and one representative bounded query analysis per metric group;
35
+ - at most `apm_query_inspection_max_queries` database inspections per Rails subscriber.
33
36
 
34
- New groups beyond capacity are dropped and counted in `dropped_groups`. Existing groups continue accumulating. The state is process-local and is lost on restart.
37
+ New groups beyond capacity are dropped and counted in `dropped_groups`. Tracking loss is reported through `dropped_trace_trackers`, `expired_trace_trackers`, and `dropped_query_fingerprints`. Existing groups continue accumulating. The state is process-local and is lost on restart.
35
38
 
36
39
  ## Breakdown
37
40
 
@@ -51,6 +54,16 @@ Chronos.configure do |config|
51
54
  config.apm_long_transaction_threshold_ms = 1000.0
52
55
  config.apm_n_plus_one_threshold = 5
53
56
  config.apm_histogram_buckets = [5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0]
57
+ config.apm_trace_ttl_seconds = 60.0
58
+ config.apm_query_analysis_enabled = true
59
+ config.apm_query_analysis_max_queries = 100
60
+ config.apm_query_inspection_enabled = false
61
+ config.apm_query_statistics_enabled = false
62
+ config.apm_query_plan_enabled = false
63
+ config.apm_query_inspection_min_duration_ms = 500.0
64
+ config.apm_query_inspection_max_queries = 20
65
+ config.apm_transaction_tracking_enabled = true
66
+ config.apm_transaction_max_connections = 100
54
67
  end
55
68
  ```
56
69
 
@@ -4,7 +4,7 @@ Version `0.6.0.pre.1` starts the legacy jobs line with optional Sidekiq 4 and 5
4
4
 
5
5
  ```ruby
6
6
  gem "sidekiq", "~> 5.0"
7
- gem "chronos-ruby", "~> 1.0.0", :require => "chronos/sidekiq"
7
+ gem "chronos-ruby", "~> 1.1.0", :require => "chronos/sidekiq"
8
8
  ```
9
9
 
10
10
  `chronos/sidekiq` installs middleware through the public `configure_client` and `configure_server` APIs. It does nothing when Sidekiq is unavailable, and the core gem never requires Sidekiq. Installation adds no Chronos thread or Redis/database connection per job; delivery continues through the agent's existing fixed worker pool.
@@ -1,22 +1,76 @@
1
1
  # Monitoramento SQL
2
2
 
3
- ## Problema e limite
3
+ ## Problema e limites
4
4
 
5
- O monitoramento SQL mede operação, tabela e duração sem transmitir SQL bruto ou binds. Sinais locais de lentidão, repetição, possível N+1, transação longa, conexão e deadlock são diagnósticos heurísticos.
5
+ O monitoramento SQL mede padrões de acesso e duração sem transmitir SQL bruto ou binds. Ele identifica query lenta/repetida, possível N+1, transação longa, famílias de erro e candidatos a índice. Recomendações são evidências heurísticas: a gem não cria índices, não executa DDL, não usa `EXPLAIN ANALYZE` e não substitui revisão do DBA.
6
6
 
7
7
  ## Fluxo e classes
8
8
 
9
- `Rails::NotificationsSubscriber` extrai somente campos permitidos. `Core::SqlNormalizer` remove comentários e literais, limita identificadores e calcula fingerprint SHA-256. `ApmAggregator` mantém grupos e fingerprints por trace com capacidades fixas, então drena lotes pelo pipeline comum.
9
+ ```mermaid
10
+ flowchart LR
11
+ AS[sql.active_record] --> N[SqlNormalizer]
12
+ N --> A[SqlQueryAnalyzer]
13
+ A --> M[ApmAggregator]
14
+ I[ActiveRecordQueryInspector opt-in] -->|índices, estatística, EXPLAIN allowlisted| A
15
+ M --> B[metric_batch sanitizado]
16
+ ```
17
+
18
+ `Rails::NotificationsSubscriber` extrai campos permitidos. `Core::SqlNormalizer` remove comentários e literais, limita identificadores e calcula fingerprint SHA-256. `Core::SqlQueryAnalyzer` analisa somente o `SELECT` normalizado, extrai tabelas e colunas de igualdade, faixa, join e ordenação e produz candidatos limitados. `Rails::ActiveRecordQueryInspector` implementa a porta `Ports::QueryInspector` e, quando habilitado, compara o candidato com índices existentes, lê estimativa de linhas e obtém plano sem executar a consulta.
19
+
20
+ O inspector envia somente nomes limitados de tabela/índice/coluna, estimativa de linhas e nós de plano allowlisted: tipo, tabela, índice, custo e linhas estimadas. Predicados, filtros, valores, SQL original e mensagens de erro são descartados. Falhas viram apenas classe limitada em um diagnóstico `error`.
21
+
22
+ `Application::ApmAggregator` agrega no grupo da query:
23
+
24
+ - `severity_counts`: `error`, `warning`, `info` e `suggestion`;
25
+ - `diagnostics`: até 20 diagnósticos estáveis com código, categoria, evidência e contagem;
26
+ - `query_analysis`: uma análise representativa, preferindo a observação com inspeção mais rica;
27
+ - `signals`: contadores compatíveis de slow query, repetição, N+1, transação e erros;
28
+ - duração total/mínima/máxima/média, histograma e p50/p95/p99 aproximados.
29
+
30
+ ## Severidades
31
+
32
+ | Severidade | Exemplos |
33
+ |---|---|
34
+ | `error` | exceção SQL, conexão, pool/statement/lock timeout, deadlock, constraint e falha da inspeção |
35
+ | `warning` | query lenta, transação longa, possível N+1 e sequential scan observado |
36
+ | `info` | padrão normalizado analisado, query repetida e índice existente que cobre o padrão |
37
+ | `suggestion` | candidato de índice e uso de eager/batch loading para possível N+1 |
38
+
39
+ No projeto consumidor, cada item de `diagnostics` pode ser persistido como informação associada à métrica usando `code`, `severity`, `category`, `message`, `recommendation`, `evidence` e `count`. `code + severity + evidence.table + evidence.columns` forma a identidade agregada atual. O consumidor deve usar `severity` para apresentação e ordenação, não inferir severidade pelo texto. Campos desconhecidos devem ser preservados ou ignorados para manter compatibilidade aditiva.
10
40
 
11
- ## Extensão, riscos e exemplo
41
+ Um candidato permanece `unverified` até o catálogo ser consultado. Ele se torna `covered` quando um índice possui o prefixo observado ou `missing` quando índices foram lidos e nenhum cobre esse prefixo. Seletividade, custo de escrita e distribuição de dados ainda precisam ser avaliados antes de qualquer migração.
12
42
 
13
- Thresholds podem ser configurados, mas dimensões de alta cardinalidade não devem ser adicionadas. Nomes de schema, tabela e coluna ainda podem revelar domínio e precisam de avaliação LGPD.
43
+ ## Configuração segura
14
44
 
15
45
  ```ruby
16
46
  Chronos.configure do |config|
17
47
  config.apm_slow_query_threshold_ms = 500.0
18
48
  config.apm_n_plus_one_threshold = 5
49
+ config.apm_trace_ttl_seconds = 60.0
50
+ config.apm_query_analysis_enabled = true
51
+ config.apm_query_analysis_max_queries = 100
52
+
53
+ # Opt-in por adicionar consultas de somente leitura ao banco.
54
+ config.apm_query_inspection_enabled = true
55
+ config.apm_query_statistics_enabled = true
56
+ config.apm_query_plan_enabled = true
57
+ config.apm_query_inspection_min_duration_ms = 500.0
58
+ config.apm_query_inspection_max_queries = 20
19
59
  end
20
60
  ```
21
61
 
22
- Veja `spec/unit/core/sql_normalizer_spec.rb`, `spec/unit/application/apm_aggregator_spec.rb` e `spec/integration/rails_telemetry_delivery_spec.rb`.
62
+ A análise normalizada é ativada por padrão, não consulta o banco e é calculada uma vez por fingerprint até o limite do cache. Inspeção, estatísticas e plano são desativados por padrão. Cada fingerprint é inspecionado no máximo uma vez por subscriber até o limite configurado; uma inspeção posterior pode enriquecer a análise estática já armazenada. PostgreSQL usa `pg_class` e `EXPLAIN (FORMAT JSON)`; MySQL/Trilogy usa `information_schema` e `EXPLAIN`. Adapters não reconhecidos ainda recebem análise estática e índices caso exponham `connection.indexes`.
63
+
64
+ ## Transações e N+1
65
+
66
+ O subscriber mede o intervalo aproximado entre os callbacks de `BEGIN`/`START TRANSACTION` e `COMMIT`/`ROLLBACK`, por identidade local da conexão. Savepoints não encerram a transação externa. Estados ociosos expiram e conexões rastreadas são limitadas. O valor não inclui o tempo gasto antes do callback de `BEGIN` nem depois do callback final.
67
+
68
+ Possível N+1 exige `SELECT` não cacheado, mesmo fingerprint, mesmo trace e repetição até o threshold. A sugestão recomenda eager loading ou carregamento em lote, mas o consumidor deve confirmar a semântica antes da alteração.
69
+
70
+ ## Riscos e extensão
71
+
72
+ `EXPLAIN` ainda usa planner, conexão e locks leves de catálogo; por isso é opt-in e selecionado por duração. Não existe timeout portátil entre os adapters legacy, então habilite primeiro em staging e monitore o orçamento. Nomes de schema/tabela/coluna/índice podem revelar o domínio e precisam de avaliação LGPD.
73
+
74
+ Um inspector alternativo pode implementar `call(raw_sql, query, options)` e retornar `indexes`, `statistics`, `plan` e `errors` nos limites do contrato. Ele deve ser somente leitura e nunca devolver valores ou mensagens livres.
75
+
76
+ Os testes principais são `spec/unit/core/sql_query_analyzer_spec.rb`, `spec/unit/rails/active_record_query_inspector_spec.rb`, `spec/unit/application/apm_aggregator_spec.rb` e `spec/integration/rails_telemetry_delivery_spec.rb`. O exemplo executável está em `examples/plain-ruby/query_analysis.rb`.
@@ -6,6 +6,6 @@ Telemetry has the same `schema_version`, event ID, timestamps, project, environm
6
6
 
7
7
  `Chronos.record_event` is the narrow integration entry point. Application code should prefer documented higher-level APIs; its payload is allowlisted by each bundled integration and then sanitized. Remote configuration can reduce or disable any locally enabled telemetry type but cannot enable a type excluded by local configuration.
8
8
 
9
- Version 0.8 aggregates request, query, job, and enabled external HTTP observations into `metric_batch` events by default. Cache remains an individual event while contributing to an existing traced request breakdown. Dependencies use one separate event per agent. Set `apm_enabled = false` only when individual legacy telemetry is required for diagnostics. Percentiles remain server-side; see [Essential APM aggregation](apm-aggregation.md).
9
+ Version 0.8 aggregates request, query, job, and enabled external HTTP observations into `metric_batch` events by default. Cache remains an individual event while contributing to an existing traced request breakdown. Dependencies use one separate event per agent. Set `apm_enabled = false` only when individual legacy telemetry is required for diagnostics. The agent emits histogram-derived approximate p50/p95/p99 while the SaaS may calculate more precise merged values; see [Essential APM aggregation](apm-aggregation.md).
10
10
 
11
11
  Version 0.9 delivers deploy telemetry synchronously because deployment commands are short-lived. Every telemetry and exception envelope contains the fixed correlation fields documented in [Deploy tracking and release correlation](deploy-tracking.md).
data/docs/performance.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Performance
2
2
 
3
- Performance is a functional requirement, but version 1.0 makes no unverified speed claim.
3
+ Performance is a functional requirement, but version 1.1 makes no unverified speed claim.
4
4
 
5
5
  Current controls:
6
6
 
@@ -18,9 +18,11 @@ Current controls:
18
18
  - shutdown and flush have caller-controlled timeouts;
19
19
  - request context and breadcrumbs have fixed structural and byte limits;
20
20
  - Rack middleware never consumes request or response bodies.
21
- - Rails subscribers copy only small allowlisted field sets and never copy raw SQL or job arguments.
21
+ - Rails subscribers copy only small allowlisted field sets and never deliver raw SQL or job arguments.
22
22
  - Sidekiq middleware creates no per-job thread or connection and bounds arguments, collections, nesting, strings, and tags before telemetry capture.
23
23
  - APM group, trace, query-fingerprint, histogram, and batch counts are fixed; no APM timer thread is created.
24
+ - active trace trackers survive aggregate drains but expire after `apm_trace_ttl_seconds`; query inspections and transaction connections have independent fixed caps;
25
+ - normalized query analysis is bounded and database inspection is disabled by default; opt-in index/statistics/plan inspection adds database round trips only once per selected fingerprint;
24
26
  - outbound HTTP instrumentation uses two clock reads and bounded metadata without body/header traversal;
25
27
  - cache normalization is bounded and SHA-256 runs only when explicitly enabled;
26
28
  - dependency inventory runs at most once per agent and is capped at 200 loaded specs.
@@ -29,7 +31,21 @@ Current controls:
29
31
 
30
32
  Run the scripts under `benchmarks/` and record Ruby version, operating system, CPU, warmup, iteration count, median, and dispersion before publishing results. `benchmarks/filtering.rb` measures privacy filtering, `benchmarks/retry_backlog.rb` measures fixed-memory outage behavior, `benchmarks/request_overhead.rb` compares Rack-protocol calls, and `benchmarks/rails_notifications.rb` isolates subscriber normalization overhead.
31
33
 
32
- ## Version 1.0.0 release gates
34
+ `benchmarks/query_analysis.rb` compares normalization alone with normalization plus bounded static analysis. It deliberately excludes database inspection because catalog and planner cost must be measured against the actual adapter, schema, statistics, and database host before production enablement.
35
+
36
+ ```bash
37
+ ITERATIONS=100000 WARMUP=5000 bundle exec ruby benchmarks/query_analysis.rb
38
+ ```
39
+
40
+ ## Version 1.1.0 release gates
41
+
42
+ Version 1.1 adds a release-gate benchmark for normalized SQL analysis. Static analysis is calculated once per fingerprint and cached under `apm_query_analysis_max_queries`; database inspection remains disabled in the benchmark because its cost depends on the actual adapter, schema, statistics, network, and database host.
43
+
44
+ The tag workflow runs query analysis with 1,000 warmup and 10,000 measured iterations, then repeats the existing Rack comparison and 500-event fake-endpoint privacy/load gate. Local preparation evidence is recorded in [Version 1.1 readiness](release-1.1-readiness.md). Results are environment-specific and are not a cross-runtime performance promise.
45
+
46
+ The local Ruby 2.2.10 preparation run measured 453.981 µs per normalization, 594.862 µs for the first static analysis of a fingerprint, 43.946 µs median incremental Rack work, and 500/500 fake-endpoint deliveries at 378.15 events/s. Static analysis is cached; the 594.862 µs value is not paid for every repeated fingerprint.
47
+
48
+ ## Historical version 1.0.0 release gates
33
49
 
34
50
  `benchmarks/comparative.rb` compares the same successful Rack fixture without and with Chronos instrumentation. It performs configurable warmup, at least three samples, and reports median plus median absolute deviation. `benchmarks/fake_endpoint_load.rb` sends asynchronous exception events to a local TCP endpoint, verifies the v1 schema marker, ensures the secret key is absent from every payload, and fails on loss, rejection, invalid payload, or timeout.
35
51