Specify what aggregate_count emits
The last unanswered action, and the only one whose output is not the input with edits -- it emits a record that never existed, which is why it was deferred twice. It emits the window's first record unchanged, tagged with cairnobs.aggregated, cairnobs.count, and the observed window bounds. That follows the convention the agent already uses for heartbeat and host-metrics records rather than inventing a second synthetic-record mechanism, and keeping the first record intact means a reader sees a real example of what was collapsed instead of an invented summary. It tags even when the count is one. Emitting a bare record there would be tidier and would make cairnobs.count present only sometimes, so summing it silently breaks on quiet windows. window_last is the last record that actually contributed, never window_start + window_ms, because a window flushed early must not claim an end that never happened. Specifying it surfaced a problem the other nine actions do not have. Windows are measured on record time, so a window can only be closed by a later record arriving. suppress_duplicates never has anything pending; aggregate_count holds state, so a matching stream that goes quiet leaves its aggregate unemitted indefinitely -- data loss dressed as latency. Emission therefore has a second trigger, end of stream, which the corpus defines as an implicit flush after the last input and which production gets from the batch flush. The cost is stated rather than hidden: window_ms becomes a maximum, not a guarantee, and one burst can produce more than one aggregate. And it has a consequence nobody should meet in production first: stats count undercounts aggregated data silently, so every panel and alert counting rows changes meaning the moment a rule aggregates the data behind it. Nothing here fixes that. The correct idiom is summing cairnobs.count; teaching the query layer to do it automatically is a Phase 2 change to the IR, recorded as the open question this decision leaves in its place rather than quietly inherited. Six cases added, corpus at 45. The validator's unspecified-action guard stays in place with an empty set, still rejecting anything added to it. Signed-off-by: John Coffey <[email protected]>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "aggregate_count_closes_a_window_on_a_later_record",
|
||||
"description": "A record at or past window_start + window_ms flushes the pending aggregate first, then opens the next window. The second window is flushed at end of stream.",
|
||||
"rules": [
|
||||
{
|
||||
"match": [],
|
||||
"actions": [
|
||||
{
|
||||
"action": "aggregate_count",
|
||||
"window_ms": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"timestamp_unix_nano": 0,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "x",
|
||||
"attributes": {}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 1000000000,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "x",
|
||||
"attributes": {}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 6000000000,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "x",
|
||||
"attributes": {}
|
||||
}
|
||||
],
|
||||
"expect": [
|
||||
{
|
||||
"timestamp_unix_nano": 0,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "x",
|
||||
"attributes": {
|
||||
"cairnobs.aggregated": "true",
|
||||
"cairnobs.count": "2",
|
||||
"cairnobs.window_start_unix_nano": "0",
|
||||
"cairnobs.window_last_unix_nano": "1000000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 6000000000,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "x",
|
||||
"attributes": {
|
||||
"cairnobs.aggregated": "true",
|
||||
"cairnobs.count": "1",
|
||||
"cairnobs.window_start_unix_nano": "6000000000",
|
||||
"cairnobs.window_last_unix_nano": "6000000000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "aggregate_count_collapses_a_window_into_one_tagged_record",
|
||||
"description": "The emitted record is the window's first record unchanged, plus the aggregate attributes. Keeping the first record means a reader sees a real example of what was collapsed rather than an invented summary.",
|
||||
"rules": [
|
||||
{
|
||||
"match": [],
|
||||
"actions": [
|
||||
{
|
||||
"action": "aggregate_count",
|
||||
"window_ms": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"timestamp_unix_nano": 0,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "repeated",
|
||||
"attributes": {}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 1000000000,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "repeated",
|
||||
"attributes": {}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 2000000000,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "repeated",
|
||||
"attributes": {}
|
||||
}
|
||||
],
|
||||
"expect": [
|
||||
{
|
||||
"timestamp_unix_nano": 0,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "repeated",
|
||||
"attributes": {
|
||||
"cairnobs.aggregated": "true",
|
||||
"cairnobs.count": "3",
|
||||
"cairnobs.window_start_unix_nano": "0",
|
||||
"cairnobs.window_last_unix_nano": "2000000000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "aggregate_count_honours_key_fields",
|
||||
"description": "With key_fields, each key gets its own window and its own aggregate. Emission order at end of stream follows the order the windows were opened.",
|
||||
"rules": [
|
||||
{
|
||||
"match": [],
|
||||
"actions": [
|
||||
{
|
||||
"action": "aggregate_count",
|
||||
"window_ms": 5000,
|
||||
"key_fields": [
|
||||
"host",
|
||||
"message"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"timestamp_unix_nano": 0,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "z",
|
||||
"attributes": {}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 100000000,
|
||||
"host": "h2",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "z",
|
||||
"attributes": {}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 200000000,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "z",
|
||||
"attributes": {}
|
||||
}
|
||||
],
|
||||
"expect": [
|
||||
{
|
||||
"timestamp_unix_nano": 0,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "z",
|
||||
"attributes": {
|
||||
"cairnobs.aggregated": "true",
|
||||
"cairnobs.count": "2",
|
||||
"cairnobs.window_start_unix_nano": "0",
|
||||
"cairnobs.window_last_unix_nano": "200000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 100000000,
|
||||
"host": "h2",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "z",
|
||||
"attributes": {
|
||||
"cairnobs.aggregated": "true",
|
||||
"cairnobs.count": "1",
|
||||
"cairnobs.window_start_unix_nano": "100000000",
|
||||
"cairnobs.window_last_unix_nano": "100000000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "aggregate_count_leaves_non_matching_records_alone",
|
||||
"description": "Records the rule did not match pass through untouched and in order, interleaved with the aggregate the matched ones produced.",
|
||||
"rules": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"field": "service",
|
||||
"op": "eq",
|
||||
"value": "noisy"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"action": "aggregate_count",
|
||||
"window_ms": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"timestamp_unix_nano": 0,
|
||||
"host": "h1",
|
||||
"service": "noisy",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "n",
|
||||
"attributes": {}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 100000000,
|
||||
"host": "h1",
|
||||
"service": "quiet",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "keep me",
|
||||
"attributes": {}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 200000000,
|
||||
"host": "h1",
|
||||
"service": "noisy",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "n",
|
||||
"attributes": {}
|
||||
}
|
||||
],
|
||||
"expect": [
|
||||
{
|
||||
"timestamp_unix_nano": 100000000,
|
||||
"host": "h1",
|
||||
"service": "quiet",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "keep me",
|
||||
"attributes": {}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 0,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "n",
|
||||
"attributes": {
|
||||
"cairnobs.aggregated": "true",
|
||||
"cairnobs.count": "2",
|
||||
"cairnobs.window_start_unix_nano": "0",
|
||||
"cairnobs.window_last_unix_nano": "200000000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "aggregate_count_tags_even_a_single_occurrence",
|
||||
"description": "A window that saw one record is still tagged with count 1. Emitting a bare record here would make cairnobs.count present only sometimes, which silently breaks summing it on quiet windows.",
|
||||
"rules": [
|
||||
{
|
||||
"match": [],
|
||||
"actions": [
|
||||
{
|
||||
"action": "aggregate_count",
|
||||
"window_ms": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"timestamp_unix_nano": 0,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "lonely",
|
||||
"attributes": {}
|
||||
}
|
||||
],
|
||||
"expect": [
|
||||
{
|
||||
"timestamp_unix_nano": 0,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "lonely",
|
||||
"attributes": {
|
||||
"cairnobs.aggregated": "true",
|
||||
"cairnobs.count": "1",
|
||||
"cairnobs.window_start_unix_nano": "0",
|
||||
"cairnobs.window_last_unix_nano": "0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "aggregate_count_window_bounds_are_observed_not_nominal",
|
||||
"description": "window_last is the last record that actually contributed, never window_start + window_ms. A window flushed early must not claim an end that never happened.",
|
||||
"rules": [
|
||||
{
|
||||
"match": [],
|
||||
"actions": [
|
||||
{
|
||||
"action": "aggregate_count",
|
||||
"window_ms": 60000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"timestamp_unix_nano": 100000000,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "y",
|
||||
"attributes": {}
|
||||
},
|
||||
{
|
||||
"timestamp_unix_nano": 250000000,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "y",
|
||||
"attributes": {}
|
||||
}
|
||||
],
|
||||
"expect": [
|
||||
{
|
||||
"timestamp_unix_nano": 100000000,
|
||||
"host": "h1",
|
||||
"service": "s1",
|
||||
"severity": "SEVERITY_INFO",
|
||||
"message": "y",
|
||||
"attributes": {
|
||||
"cairnobs.aggregated": "true",
|
||||
"cairnobs.count": "2",
|
||||
"cairnobs.window_start_unix_nano": "100000000",
|
||||
"cairnobs.window_last_unix_nano": "250000000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user