Skip to content

Commit dffe56c

Browse files
Add summary to journeys which don't emit journey:end (early node subprocess exits) (#29606) (#29812)
* update link to beats developer guide * fix: add summary to journeys which don't emit journey:end [fixes #28770] * fix: avoid cmd/status when journey has already finished (cherry picked from commit 3270ae1) Co-authored-by: Lucas F. da Costa <[email protected]>
1 parent c0d9dbf commit dffe56c

File tree

7 files changed

+150
-39
lines changed

7 files changed

+150
-39
lines changed

CHANGELOG.next.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
1919
*Heartbeat*
2020

2121
- Only add monitor.status to browser events when summary. {pull}29460[29460]
22+
- Also add summary to journeys for which the synthetics runner crashes. {pull}29606[29606]
2223

2324
*Metricbeat*
2425

libbeat/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ If you want to create a new project that reads some sort of operational data
88
and ships it to Elasticsearch, we suggest you make use of this library. Please
99
start by reading our [CONTRIBUTING](../CONTRIBUTING.md) file. We also have a
1010
[developer
11-
guide](https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html) to
11+
guide](https://www.elastic.co/guide/en/beats/devguide/master/index.html) to
1212
help you with the creation of new Beats.
1313

1414
Please also open a topic on the [forums](https://discuss.elastic.co/c/beats/libbeat) and

x-pack/heartbeat/monitors/browser/synthexec/enrich.go

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ func (je *journeyEnricher) enrichSynthEvent(event *beat.Event, se *SynthEvent) e
112112
}
113113

114114
switch se.Type {
115+
case "cmd/status":
116+
// If a command failed _after_ the journey was complete, as it happens
117+
// when an `afterAll` hook fails, for example, we don't wan't to include
118+
// a summary in the cmd/status event.
119+
if !je.journeyComplete {
120+
je.end = event.Timestamp
121+
return je.createSummary(event)
122+
}
115123
case "journey/end":
116124
je.journeyComplete = true
117125
return je.createSummary(event)
@@ -155,23 +163,24 @@ func (je *journeyEnricher) createSummary(event *beat.Event) error {
155163
down = 0
156164
}
157165

158-
if je.journeyComplete {
159-
eventext.MergeEventFields(event, common.MapStr{
160-
"url": je.urlFields,
161-
"synthetics": common.MapStr{
162-
"type": "heartbeat/summary",
163-
"journey": je.journey,
164-
},
165-
"monitor": common.MapStr{
166-
"duration": common.MapStr{
167-
"us": int64(je.end.Sub(je.start) / time.Microsecond),
168-
},
169-
},
170-
"summary": common.MapStr{
171-
"up": up,
172-
"down": down,
166+
eventext.MergeEventFields(event, common.MapStr{
167+
"url": je.urlFields,
168+
"synthetics": common.MapStr{
169+
"type": "heartbeat/summary",
170+
"journey": je.journey,
171+
},
172+
"monitor": common.MapStr{
173+
"duration": common.MapStr{
174+
"us": int64(je.end.Sub(je.start) / time.Microsecond),
173175
},
174-
})
176+
},
177+
"summary": common.MapStr{
178+
"up": up,
179+
"down": down,
180+
},
181+
})
182+
183+
if je.journeyComplete {
175184
return je.firstError
176185
}
177186

x-pack/heartbeat/monitors/browser/synthexec/enrich_test.go

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ import (
2121
"github.com/elastic/go-lookslike/testslike"
2222
)
2323

24+
func makeStepEvent(typ string, ts float64, name string, index int, status string, urlstr string, err *SynthError) *SynthEvent {
25+
return &SynthEvent{
26+
Type: typ,
27+
TimestampEpochMicros: 1000 + ts,
28+
PackageVersion: "1.0.0",
29+
Step: &Step{Name: name, Index: index, Status: status},
30+
Error: err,
31+
Payload: common.MapStr{},
32+
URL: urlstr,
33+
}
34+
}
35+
2436
func TestJourneyEnricher(t *testing.T) {
2537
journey := &Journey{
2638
Name: "A Journey Name",
@@ -50,17 +62,6 @@ func TestJourneyEnricher(t *testing.T) {
5062
Journey: journey,
5163
Payload: common.MapStr{},
5264
}
53-
makeStepEvent := func(typ string, ts float64, name string, index int, status string, urlstr string, err *SynthError) *SynthEvent {
54-
return &SynthEvent{
55-
Type: typ,
56-
TimestampEpochMicros: 1000 + ts,
57-
PackageVersion: "1.0.0",
58-
Step: &Step{Name: name, Index: index, Status: status},
59-
Error: err,
60-
Payload: common.MapStr{},
61-
URL: urlstr,
62-
}
63-
}
6465
url1 := "http://example.net/url1"
6566
url2 := "http://example.net/url2"
6667
url3 := "http://example.net/url3"
@@ -121,6 +122,24 @@ func TestEnrichSynthEvent(t *testing.T) {
121122
wantErr bool
122123
check func(t *testing.T, e *beat.Event, je *journeyEnricher)
123124
}{
125+
{
126+
"cmd/status",
127+
&journeyEnricher{},
128+
&SynthEvent{
129+
Type: "cmd/status",
130+
Error: &SynthError{Name: "cmdexit", Message: "cmd err msg"},
131+
},
132+
true,
133+
func(t *testing.T, e *beat.Event, je *journeyEnricher) {
134+
v := lookslike.MustCompile(map[string]interface{}{
135+
"summary": map[string]int{
136+
"up": 0,
137+
"down": 1,
138+
},
139+
})
140+
testslike.Test(t, v, e.Fields)
141+
},
142+
},
124143
{
125144
"journey/end",
126145
&journeyEnricher{},
@@ -195,3 +214,75 @@ func TestEnrichSynthEvent(t *testing.T) {
195214
})
196215
}
197216
}
217+
218+
func TestNoSummaryOnAfterHook(t *testing.T) {
219+
journey := &Journey{
220+
Name: "A journey that fails after completing",
221+
Id: "my-bad-after-all-hook",
222+
}
223+
journeyStart := &SynthEvent{
224+
Type: "journey/start",
225+
TimestampEpochMicros: 1000,
226+
PackageVersion: "1.0.0",
227+
Journey: journey,
228+
Payload: common.MapStr{},
229+
}
230+
syntherr := &SynthError{
231+
Message: "my-errmsg",
232+
Name: "my-errname",
233+
Stack: "my\nerr\nstack",
234+
}
235+
journeyEnd := &SynthEvent{
236+
Type: "journey/end",
237+
TimestampEpochMicros: 2000,
238+
PackageVersion: "1.0.0",
239+
Journey: journey,
240+
Payload: common.MapStr{},
241+
}
242+
cmdStatus := &SynthEvent{
243+
Type: "cmd/status",
244+
Error: &SynthError{Name: "cmdexit", Message: "cmd err msg"},
245+
TimestampEpochMicros: 3000,
246+
}
247+
248+
badStepUrl := "https://example.com/bad-step"
249+
synthEvents := []*SynthEvent{
250+
journeyStart,
251+
makeStepEvent("step/start", 10, "Step1", 1, "", "", nil),
252+
makeStepEvent("step/end", 20, "Step1", 1, "failed", badStepUrl, syntherr),
253+
journeyEnd,
254+
cmdStatus,
255+
}
256+
257+
je := &journeyEnricher{}
258+
259+
for idx, se := range synthEvents {
260+
e := &beat.Event{}
261+
t.Run(fmt.Sprintf("event %d", idx), func(t *testing.T) {
262+
enrichErr := je.enrich(e, se)
263+
264+
if se != nil && se.Type == "cmd/status" {
265+
t.Run("no summary in cmd/status", func(t *testing.T) {
266+
require.NotContains(t, e.Fields, "summary")
267+
})
268+
}
269+
270+
// Only the journey/end event should get a summary when
271+
// it's emitted before the cmd/status (when an afterX hook fails).
272+
if se != nil && se.Type == "journey/end" {
273+
require.Equal(t, stepError(syntherr), enrichErr)
274+
275+
u, _ := url.Parse(badStepUrl)
276+
t.Run("summary in journey/end", func(t *testing.T) {
277+
v := lookslike.MustCompile(common.MapStr{
278+
"synthetics.type": "heartbeat/summary",
279+
"url": wrappers.URLFields(u),
280+
"monitor.duration.us": int64(journeyEnd.Timestamp().Sub(journeyStart.Timestamp()) / time.Microsecond),
281+
})
282+
283+
testslike.Test(t, v, e.Fields)
284+
})
285+
}
286+
})
287+
}
288+
}

x-pack/heartbeat/monitors/browser/synthexec/execmultiplexer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func (e ExecMultiplexer) writeSynthEvent(se *SynthEvent) {
2929
e.eventCounter.Store(-1)
3030
}
3131
hasCurrentJourney := e.currentJourney.Load()
32-
if se.Type == "journey/end" {
32+
if se.Type == "journey/end" || se.Type == "cmd/status" {
3333
e.currentJourney.Store(false)
3434
}
3535

x-pack/heartbeat/monitors/browser/synthexec/execmultiplexer_test.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestExecMultiplexer(t *testing.T) {
1818
var testJourneys []*Journey
1919
var testEvents []*SynthEvent
2020
time := float64(0)
21-
for jIdx := 0; jIdx < 3; jIdx++ {
21+
for jIdx := 0; jIdx < 4; jIdx++ {
2222
time++ // fake time to make events seem spaced out
2323
journey := &Journey{
2424
Name: fmt.Sprintf("J%d", jIdx),
@@ -45,11 +45,20 @@ func TestExecMultiplexer(t *testing.T) {
4545
})
4646
}
4747

48-
testEvents = append(testEvents, &SynthEvent{
49-
Journey: journey,
50-
Type: "journey/end",
51-
TimestampEpochMicros: time,
52-
})
48+
// We want one of the test journeys to end with a cmd/status indicating it failed
49+
if jIdx != 4 {
50+
testEvents = append(testEvents, &SynthEvent{
51+
Journey: journey,
52+
Type: "journey/end",
53+
TimestampEpochMicros: time,
54+
})
55+
} else {
56+
testEvents = append(testEvents, &SynthEvent{
57+
Journey: journey,
58+
Type: "cmd/status",
59+
TimestampEpochMicros: time,
60+
})
61+
}
5362
}
5463

5564
// Write the test events in another go routine since writes block
@@ -77,7 +86,7 @@ Loop:
7786
i := 0 // counter for index, resets on journey change
7887
for _, se := range results {
7988
require.Equal(t, i, se.index)
80-
if se.Type == "journey/end" {
89+
if se.Type == "journey/end" || se.Type == "cmd/status" {
8190
i = 0
8291
} else {
8392
i++

x-pack/heartbeat/monitors/browser/synthexec/synthexec.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,9 @@ func runCmd(
196196
if err != nil {
197197
str := fmt.Sprintf("command exited with status %d: %s", cmd.ProcessState.ExitCode(), err)
198198
mpx.writeSynthEvent(&SynthEvent{
199-
Type: "cmd/status",
200-
Error: &SynthError{Name: "cmdexit", Message: str},
199+
Type: "cmd/status",
200+
Error: &SynthError{Name: "cmdexit", Message: str},
201+
TimestampEpochMicros: float64(time.Now().UnixMicro()),
201202
})
202203
logp.Warn("Error executing command '%s' (%d): %s", loggableCmd.String(), cmd.ProcessState.ExitCode(), err)
203204
}
@@ -243,7 +244,7 @@ func lineToSynthEventFactory(typ string) func(bytes []byte, text string) (res *S
243244
logp.Info("%s: %s", typ, text)
244245
return &SynthEvent{
245246
Type: typ,
246-
TimestampEpochMicros: float64(time.Now().UnixNano() / int64(time.Millisecond)),
247+
TimestampEpochMicros: float64(time.Now().UnixMicro()),
247248
Payload: map[string]interface{}{
248249
"message": text,
249250
},

0 commit comments

Comments
 (0)