close
Skip to content

feat(protocols): add duration fields to other events#7428

Open
dwisiswant0 wants to merge 3 commits into
devfrom
dwisiswant0/feat/protocol/add-duration-fields-to-other-events
Open

feat(protocols): add duration fields to other events#7428
dwisiswant0 wants to merge 3 commits into
devfrom
dwisiswant0/feat/protocol/add-duration-fields-to-other-events

Conversation

@dwisiswant0
Copy link
Copy Markdown
Member

@dwisiswant0 dwisiswant0 commented May 26, 2026

Proposed changes

Add matcher and extractor duration data[1] for
DNS, SSL, WHOIS, TCP, WebSocket, and Headless
requests.

Track step durations for TCP inputs, WebSocket
message exchanges, and Headless actions. The
duration field holds the most recently measured
operation.

Expose request ID-prefixed duration values through
the existing template context path, and add
protocol tests for duration extraction, aliases,
and request ID handling.

[1]: The data exposed is duration, duration_N,
<request-id>_duration, and
<request-id>_duration_<N>.

Closes #5413
Closes #7422

Proof

Checklist

  • Pull request is created against the dev branch
  • All checks passed (lint, unit/integration/regression tests etc.) with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Summary by CodeRabbit

  • New Features

    • Added duration measurements (including per-step/action timings) to DNS, Network, Headless, HTTP, SSL, WebSocket and WHOIS requests; durations are exposed to templates and matchers.
  • Bug Fixes

    • Protocol requests now report their configured request IDs correctly instead of empty identifiers.
  • Tests

    • Added and expanded unit/integration tests validating duration fields, per-step timings, and template exposure across protocols.
  • Documentation

    • Updated protocol and template docs to include duration part definitions.

Review Change Stack

Add matcher and extractor duration data[1] for
DNS, SSL, WHOIS, TCP, WebSocket, and Headless
requests.

Track step durations for TCP inputs, WebSocket
message exchanges, and Headless actions. The
`duration` field holds the most recently measured
operation.

Expose request ID-prefixed duration values through
the existing template context path, and add
protocol tests for duration extraction, aliases,
and request ID handling.

[1]: The data exposed is `duration`, `duration_N`,
    `<request-id>_duration`, and
    `<request-id>_duration_<N>`.

Closes #5413
Closes #7422

Signed-off-by: Dwi Siswanto <git@dw1.io>
@auto-assign auto-assign Bot requested a review from dogancanbakir May 26, 2026 14:01
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c8901cd9-e075-4208-b02f-a53a08fb50ec

📥 Commits

Reviewing files that changed from the base of the PR and between 57bd193 and d718223.

📒 Files selected for processing (3)
  • internal/tests/integration/duration_test.go
  • internal/tests/integration/network_test.go
  • internal/tests/integration/websocket_test.go
✅ Files skipped from review due to trivial changes (1)
  • internal/tests/integration/duration_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/tests/integration/websocket_test.go

Walkthrough

Adds runtime duration measurement across DNS, Network, Headless, SSL, WebSocket, and WHOIS protocols, emits duration and indexed duration_N fields into output events and template context, and updates tests and documentation to validate and document these timing fields.

Changes

Duration Support Across Protocols

Layer / File(s) Summary
DNS Duration Measurement and DSL Integration
pkg/protocols/dns/request.go, pkg/protocols/dns/operators.go, pkg/protocols/dns/dns.go, pkg/protocols/dns/operators_test.go, internal/tests/integration/dns_test.go
DNS execution records elapsed time, passes duration into the DSL event builder, and exposes duration in events and template context. Tests validate DSL extraction and exported template vars.
SSL and WHOIS Single-Operation Duration
pkg/protocols/ssl/ssl.go, pkg/protocols/ssl/ssl_test.go, pkg/protocols/whois/whois.go, pkg/protocols/whois/whois_test.go, internal/tests/integration/ssl_test.go, internal/tests/integration/whois_test.go
SSL handshake and WHOIS/RDAP lookups measure elapsed time, populate duration in output/context, and return configured ID from GetID(); tests assert presence and extraction of duration.
Network Multi-Step Duration Tracking
pkg/protocols/network/request.go, pkg/protocols/network/network.go, pkg/protocols/network/operators_test.go, pkg/protocols/network/request_duration_test.go, internal/tests/integration/network_test.go
Network records per-step timings, writes duration_1..N plus aggregate duration to events, and validates values and template-context mirroring in unit and integration tests.
WebSocket Multi-Step Duration with Handshake Fallback
pkg/protocols/websocket/websocket.go, pkg/protocols/websocket/websocket_test.go, internal/tests/integration/websocket_test.go
WebSocket measures handshake and per-message durations, returns step durations from the read/write loop, and populates duration/duration_N fields; tests cover multi-input and no-input scenarios.
Headless Per-Action Duration with Deferred Wait Attribution
pkg/protocols/headless/engine/page.go, pkg/protocols/headless/engine/page_actions.go, pkg/protocols/headless/engine/page_actions_test.go, pkg/protocols/headless/headless.go, pkg/protocols/headless/request.go, pkg/protocols/headless/operators_test.go, internal/tests/integration/headless_test.go
Headless pages record per-action durations in Page.ActionDurations, associate deferred waits to action indices to attribute wait durations correctly, and expose per-action duration fields; tests assert recorded timings.
HTTP Request Condition Duration Testing
pkg/protocols/http/request_condition_test.go
New tests ensure request condition detection for indexed-duration references and that duration template variable keys use Request.ID as prefix.
Templates, Generated Docs, and Syntax Reference
pkg/templates/templates_doc.go, SYNTAX-REFERENCE.md
Generated template docs and syntax reference updated to list duration in RequestPartDefinitions for DNS, Network, Headless, SSL, WebSocket, and WHOIS; includes minor HTTP doc tweaks.
Integration Tests, Helpers, and Test Additions
internal/tests/integration/*, various *_test.go helpers across protocols
Integration subtests added for duration scenarios; helpers and unit tests extended to assert duration and duration_N fields and template-context exports.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

  • dogancanbakir
  • Mzack9999

Poem

🐰
I hopped through code to mark the time,
From DNS to websockets, every chime,
Steps counted, handshakes measured too,
Now durations bloom in event-view!
🥕⌛

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(protocols): add duration fields to other events' directly and clearly describes the main change: adding duration measurement fields across multiple protocol implementations.
Linked Issues check ✅ Passed All requirements from #5413 and #7422 are met: duration matching implemented for single-step (DNS, SSL, WHOIS) and multi-step protocols (Network, WebSocket, Headless); DSL matchers, extractors, and template context exposed correctly.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing duration fields across protocols as specified. Documentation updates, test additions, and request ID fixes are all aligned with the feature objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dwisiswant0/feat/protocol/add-duration-fields-to-other-events

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/protocols/dns/request.go (1)

164-190: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Duration currently includes optional trace time

duration is calculated after the trace branch, so with trace: true it includes trace recursion time in addition to the DNS query. That skews duration matchers/extractors for this protocol operation.

Suggested fix
  // Send the request to the target servers
  timeStart := time.Now()
  response, err := dnsClient.Do(compiledRequest)
+ duration := time.Since(timeStart)
  if err != nil {
      request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
      request.options.Progress.IncrementFailedRequestsBy(1)
  } else {
      request.options.Progress.IncrementRequests()
  }
@@
- duration := time.Since(timeStart)
-
  // Create the output event
  outputEvent := request.responseToDSLMap(compiledRequest, response, domain, question, traceData, duration)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/protocols/dns/request.go` around lines 164 - 190, The duration for the
DNS query is measured after the optional trace, so when request.Trace is true
the duration includes trace recursion time; fix this by measuring the query
duration immediately after the dnsClient.Do handling (capture queryDuration :=
time.Since(timeStart) right after response is validated and before entering the
trace block), then perform the trace (and if you need a separate trace duration
use a new timer) and finally pass queryDuration into request.responseToDSLMap
(instead of a duration that includes trace). Update references to
timeStart/duration accordingly in request.requestToDSLMap call and leave trace
timing isolated in request.dnsClient.Trace handling.
pkg/protocols/network/request.go (1)

356-385: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

duration is under-measured for write-only steps and may be missing

When input.Read == 0, duration is captured right after conn.Write, but the actual response read happens later at the final read path. That makes duration effectively write-latency for common write-then-read flows, and can leave no duration fields for some paths.

Suggested direction
+ var pendingStepStart time.Time
+ hasPendingStep := false

  timeStart := time.Now()
  if _, err := conn.Write(dataInBytes); err != nil {
      ...
  }

  if input.Read > 0 {
      buffer, err := ConnReadNWithTimeout(...)
      if err != nil {
          ...
      }
      stepDurations = append(stepDurations, time.Since(timeStart))
+     hasPendingStep = false
  } else {
-     stepDurations = append(stepDurations, time.Since(timeStart))
+     pendingStepStart = timeStart
+     hasPendingStep = true
  }

+ finalReadStart := time.Now()
+ if hasPendingStep {
+     finalReadStart = pendingStepStart
+ }
  final, readErr := ConnReadNWithTimeout(...)
  ...
+ if hasPendingStep || len(stepDurations) == 0 {
+     stepDurations = append(stepDurations, time.Since(finalReadStart))
+ }

Also applies to: 415-425, 466-471

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/protocols/network/request.go` around lines 356 - 385, The timing for
request steps is being recorded too early (timeStart is measured before
conn.Write and appended immediately when input.Read == 0), causing write-only
steps to record only write latency or miss duration entirely; change the logic
so that timeStart is set before the full send/read cycle for a given input and
that stepDurations is appended only after the actual response read completes
(i.e., move or delay the append to the branch where ConnReadNWithTimeout (and
other final-read paths) finish), ensuring symbols like timeStart, conn.Write,
ConnReadNWithTimeout, responseBuilder, input.Read, stepDurations and the
post-write read branches all use the same start/stop pair so durations reflect
the full request/response roundtrip.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tests/integration/network_test.go`:
- Around line 130-142: The handler in the duration subtest must fail fast on
unexpected payloads: after reading with reader.ConnReadNWithTimeout, verify the
payloads exactly match "FIRST" and "SECOND" and if not call errState.Set with a
descriptive error and return immediately (instead of silently continuing);
update the branches around the first check (where you currently write "PING")
and the second check (where you write "PONG" and then "NUCLEI") to set
errState.Set(fmt.Errorf(...)) and return on mismatch so the subtest fails when
request sequencing/content is wrong, keeping use of conn and
reader.ConnReadNWithTimeout intact.

---

Outside diff comments:
In `@pkg/protocols/dns/request.go`:
- Around line 164-190: The duration for the DNS query is measured after the
optional trace, so when request.Trace is true the duration includes trace
recursion time; fix this by measuring the query duration immediately after the
dnsClient.Do handling (capture queryDuration := time.Since(timeStart) right
after response is validated and before entering the trace block), then perform
the trace (and if you need a separate trace duration use a new timer) and
finally pass queryDuration into request.responseToDSLMap (instead of a duration
that includes trace). Update references to timeStart/duration accordingly in
request.requestToDSLMap call and leave trace timing isolated in
request.dnsClient.Trace handling.

In `@pkg/protocols/network/request.go`:
- Around line 356-385: The timing for request steps is being recorded too early
(timeStart is measured before conn.Write and appended immediately when
input.Read == 0), causing write-only steps to record only write latency or miss
duration entirely; change the logic so that timeStart is set before the full
send/read cycle for a given input and that stepDurations is appended only after
the actual response read completes (i.e., move or delay the append to the branch
where ConnReadNWithTimeout (and other final-read paths) finish), ensuring
symbols like timeStart, conn.Write, ConnReadNWithTimeout, responseBuilder,
input.Read, stepDurations and the post-write read branches all use the same
start/stop pair so durations reflect the full request/response roundtrip.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ac4f0c48-e9b7-4958-b3e8-f005563a7633

📥 Commits

Reviewing files that changed from the base of the PR and between 0bc707a and 047f084.

⛔ Files ignored due to path filters (6)
  • internal/tests/integration/testdata/protocols/dns/duration.yaml is excluded by !**/*.yaml
  • internal/tests/integration/testdata/protocols/headless/headless-duration.yaml is excluded by !**/*.yaml
  • internal/tests/integration/testdata/protocols/network/duration.yaml is excluded by !**/*.yaml
  • internal/tests/integration/testdata/protocols/ssl/duration.yaml is excluded by !**/*.yaml
  • internal/tests/integration/testdata/protocols/websocket/duration.yaml is excluded by !**/*.yaml
  • internal/tests/integration/testdata/protocols/whois/duration.yaml is excluded by !**/*.yaml
📒 Files selected for processing (29)
  • SYNTAX-REFERENCE.md
  • internal/tests/integration/dns_test.go
  • internal/tests/integration/headless_test.go
  • internal/tests/integration/network_test.go
  • internal/tests/integration/ssl_test.go
  • internal/tests/integration/websocket_test.go
  • internal/tests/integration/whois_test.go
  • pkg/protocols/dns/dns.go
  • pkg/protocols/dns/operators.go
  • pkg/protocols/dns/operators_test.go
  • pkg/protocols/dns/request.go
  • pkg/protocols/headless/engine/page.go
  • pkg/protocols/headless/engine/page_actions.go
  • pkg/protocols/headless/engine/page_actions_test.go
  • pkg/protocols/headless/headless.go
  • pkg/protocols/headless/operators_test.go
  • pkg/protocols/headless/request.go
  • pkg/protocols/http/request_condition_test.go
  • pkg/protocols/network/network.go
  • pkg/protocols/network/operators_test.go
  • pkg/protocols/network/request.go
  • pkg/protocols/network/request_duration_test.go
  • pkg/protocols/ssl/ssl.go
  • pkg/protocols/ssl/ssl_test.go
  • pkg/protocols/websocket/websocket.go
  • pkg/protocols/websocket/websocket_test.go
  • pkg/protocols/whois/whois.go
  • pkg/protocols/whois/whois_test.go
  • pkg/templates/templates_doc.go

Comment on lines +130 to +142
if string(data) == "FIRST" {
_, _ = conn.Write([]byte("PING"))
}

data, err = reader.ConnReadNWithTimeout(conn, 6, 5*time.Second)
if err != nil {
errState.Set(err)
return
}
if string(data) == "SECOND" {
_, _ = conn.Write([]byte("PONG"))
}
_, _ = conn.Write([]byte("NUCLEI"))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fail fast on unexpected step payloads in the duration subtest.

This handler only writes on matching inputs but doesn’t mark mismatches as failures, so the subtest may still pass with wrong request sequencing/content.

Suggested fix
-			if string(data) == "FIRST" {
-				_, _ = conn.Write([]byte("PING"))
-			}
+			if string(data) == "FIRST" {
+				_, _ = conn.Write([]byte("PING"))
+			} else {
+				errState.Set(fmt.Errorf("invalid first data received: %s", string(data)))
+				return
+			}
@@
-			if string(data) == "SECOND" {
-				_, _ = conn.Write([]byte("PONG"))
-			}
+			if string(data) == "SECOND" {
+				_, _ = conn.Write([]byte("PONG"))
+			} else {
+				errState.Set(fmt.Errorf("invalid second data received: %s", string(data)))
+				return
+			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if string(data) == "FIRST" {
_, _ = conn.Write([]byte("PING"))
}
data, err = reader.ConnReadNWithTimeout(conn, 6, 5*time.Second)
if err != nil {
errState.Set(err)
return
}
if string(data) == "SECOND" {
_, _ = conn.Write([]byte("PONG"))
}
_, _ = conn.Write([]byte("NUCLEI"))
if string(data) == "FIRST" {
_, _ = conn.Write([]byte("PING"))
} else {
errState.Set(fmt.Errorf("invalid first data received: %s", string(data)))
return
}
data, err = reader.ConnReadNWithTimeout(conn, 6, 5*time.Second)
if err != nil {
errState.Set(err)
return
}
if string(data) == "SECOND" {
_, _ = conn.Write([]byte("PONG"))
} else {
errState.Set(fmt.Errorf("invalid second data received: %s", string(data)))
return
}
_, _ = conn.Write([]byte("NUCLEI"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tests/integration/network_test.go` around lines 130 - 142, The
handler in the duration subtest must fail fast on unexpected payloads: after
reading with reader.ConnReadNWithTimeout, verify the payloads exactly match
"FIRST" and "SECOND" and if not call errState.Set with a descriptive error and
return immediately (instead of silently continuing); update the branches around
the first check (where you currently write "PING") and the second check (where
you write "PONG" and then "NUCLEI") to set errState.Set(fmt.Errorf(...)) and
return on mismatch so the subtest fails when request sequencing/content is
wrong, keeping use of conn and reader.ConnReadNWithTimeout intact.

Signed-off-by: Dwi Siswanto <git@dw1.io>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/protocols/websocket/websocket_test.go`:
- Around line 289-295: The test handler is discarding errors from
wsutil.WriteServerMessage in the "hello" and "status" branches; capture the
returned error from wsutil.WriteServerMessage (in the cases handling "hello" and
"status") and if non-nil call the test failure helper (e.g., t.Fatalf or
t.Errorf and return) with a clear message including the operation and error;
ensure you reference conn, op and durationObservationDelay in the error context
so write failures fail fast and show actionable diagnostics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b8e6d385-fe17-49bc-92e7-c2c6ba104243

📥 Commits

Reviewing files that changed from the base of the PR and between 047f084 and 57bd193.

📒 Files selected for processing (2)
  • pkg/protocols/network/request_duration_test.go
  • pkg/protocols/websocket/websocket_test.go

Comment on lines +289 to +295
case "hello":
time.Sleep(durationObservationDelay)
_ = wsutil.WriteServerMessage(conn, op, []byte("world"))
case "status":
time.Sleep(durationObservationDelay)
_ = wsutil.WriteServerMessage(conn, op, []byte("ready"))
default:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle websocket write errors in the test handler.

On Line 291 and Line 294, write errors are discarded. If a server write fails, the test can fail later with less actionable symptoms.

Suggested fix
 			switch string(msg) {
 			case "hello":
 				time.Sleep(durationObservationDelay)
-				_ = wsutil.WriteServerMessage(conn, op, []byte("world"))
+				if err := wsutil.WriteServerMessage(conn, op, []byte("world")); err != nil {
+					return
+				}
 			case "status":
 				time.Sleep(durationObservationDelay)
-				_ = wsutil.WriteServerMessage(conn, op, []byte("ready"))
+				if err := wsutil.WriteServerMessage(conn, op, []byte("ready")); err != nil {
+					return
+				}
 			default:
 				return
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case "hello":
time.Sleep(durationObservationDelay)
_ = wsutil.WriteServerMessage(conn, op, []byte("world"))
case "status":
time.Sleep(durationObservationDelay)
_ = wsutil.WriteServerMessage(conn, op, []byte("ready"))
default:
case "hello":
time.Sleep(durationObservationDelay)
if err := wsutil.WriteServerMessage(conn, op, []byte("world")); err != nil {
return
}
case "status":
time.Sleep(durationObservationDelay)
if err := wsutil.WriteServerMessage(conn, op, []byte("ready")); err != nil {
return
}
default:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/protocols/websocket/websocket_test.go` around lines 289 - 295, The test
handler is discarding errors from wsutil.WriteServerMessage in the "hello" and
"status" branches; capture the returned error from wsutil.WriteServerMessage (in
the cases handling "hello" and "status") and if non-nil call the test failure
helper (e.g., t.Fatalf or t.Errorf and return) with a clear message including
the operation and error; ensure you reference conn, op and
durationObservationDelay in the error context so write failures fail fast and
show actionable diagnostics.

Signed-off-by: Dwi Siswanto <git@dw1.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Response-Time Matching For Additional Protocols Add support for the duration matcher for network templates

1 participant