Skip to content

Commit 3691083

Browse files
authored
fix: put back duration_ext metric as the top-level timer (#85)
- [x] Setup timers to be retro-compatible with the old metrics - [x] Add a decode metric Signed-off-by: Eliott Bouhana <[email protected]>
1 parent dc7f703 commit 3691083

File tree

4 files changed

+31
-47
lines changed

4 files changed

+31
-47
lines changed

context.go

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,8 @@ func (context *Context) Run(addressData RunAddressData, _ time.Duration) (res Re
117117

118118
runTimer, err := context.timer.NewNode(wafRunTag,
119119
timer.WithComponents(
120-
wafPersistentEncoderTag,
121-
wafEphemeralEncoderTag,
122-
wafDurationExtTag,
120+
wafEncodeTag,
121+
wafDecodeTag,
123122
wafDurationTag,
124123
),
125124
)
@@ -129,22 +128,28 @@ func (context *Context) Run(addressData RunAddressData, _ time.Duration) (res Re
129128

130129
runTimer.Start()
131130
defer func() {
132-
context.metrics.add("_dd.appsec.waf.run", runTimer.Stop())
131+
context.metrics.add(wafRunTag, runTimer.Stop())
133132
context.metrics.merge(runTimer.Stats())
134133
}()
135134

136-
persistentData, persistentEncoder, err := context.encodeOneAddressType(addressData.Persistent, runTimer.MustLeaf(wafPersistentEncoderTag))
135+
wafEncodeTimer := runTimer.MustLeaf(wafEncodeTag)
136+
wafEncodeTimer.Start()
137+
persistentData, persistentEncoder, err := context.encodeOneAddressType(addressData.Persistent, wafEncodeTimer)
137138
if err != nil {
139+
wafEncodeTimer.Stop()
138140
return res, err
139141
}
140142

141143
// The WAF releases ephemeral address data at the max of each run call, so we need not keep the Go values live beyond
142144
// that in the same way we need for persistent data. We hence use a separate encoder.
143-
ephemeralData, ephemeralEncoder, err := context.encodeOneAddressType(addressData.Ephemeral, runTimer.MustLeaf(wafEphemeralEncoderTag))
145+
ephemeralData, ephemeralEncoder, err := context.encodeOneAddressType(addressData.Ephemeral, wafEncodeTimer)
144146
if err != nil {
147+
wafEncodeTimer.Stop()
145148
return res, err
146149
}
147150

151+
wafEncodeTimer.Stop()
152+
148153
// ddwaf_run cannot run concurrently and we are going to mutate the context.cgoRefs, so we need to lock the context
149154
context.mutex.Lock()
150155
defer context.mutex.Unlock()
@@ -157,8 +162,8 @@ func (context *Context) Run(addressData RunAddressData, _ time.Duration) (res Re
157162
// into C ddwaf_objects. libddwaf's API requires to keep this data for the lifetime of the ddwaf_context.
158163
defer context.cgoRefs.append(persistentEncoder.cgoRefs)
159164

160-
wafExtTimer := runTimer.MustLeaf(wafDurationExtTag)
161-
res, err = context.run(persistentData, ephemeralData, wafExtTimer, runTimer.SumRemaining())
165+
wafDecodeTimer := runTimer.MustLeaf(wafDecodeTag)
166+
res, err = context.run(persistentData, ephemeralData, wafDecodeTimer, runTimer.SumRemaining())
162167

163168
runTimer.AddTime(wafDurationTag, res.TimeSpent)
164169

@@ -207,15 +212,15 @@ func merge[K comparable, V any](a, b map[K][]V) (merged map[K][]V) {
207212
// If the addressData is empty, it returns nil for the WAF object and an empty ref pool.
208213
// At this point, if the encoder does not timeout, the only error we can get is an error in case the top level object
209214
// is a nil map, but this behaviour is expected since either persistent or ephemeral addresses are allowed to be null
210-
// one at a time. In this case, EncodeAddresses will return nil contrary to Encode which will return a nil wafObject,
215+
// one at a time. In this case, Encode will return nil contrary to Encode which will return a nil wafObject,
211216
// which is what we need to send to ddwaf_run to signal that the address data is empty.
212217
func (context *Context) encodeOneAddressType(addressData map[string]any, timer timer.Timer) (*bindings.WafObject, encoder, error) {
213218
encoder := newLimitedEncoder(timer)
214219
if addressData == nil {
215220
return nil, encoder, nil
216221
}
217222

218-
data, _ := encoder.EncodeAddresses(addressData)
223+
data, _ := encoder.Encode(addressData)
219224
if len(encoder.truncations) > 0 {
220225
context.mutex.Lock()
221226
defer context.mutex.Unlock()
@@ -232,18 +237,18 @@ func (context *Context) encodeOneAddressType(addressData map[string]any, timer t
232237

233238
// run executes the ddwaf_run call with the provided data on this context. The caller is responsible for locking the
234239
// context appropriately around this call.
235-
func (context *Context) run(persistentData, ephemeralData *bindings.WafObject, wafTimer timer.Timer, timeBudget time.Duration) (Result, error) {
240+
func (context *Context) run(persistentData, ephemeralData *bindings.WafObject, wafDecodeTimer timer.Timer, timeBudget time.Duration) (Result, error) {
236241
result := new(bindings.WafResult)
237242
defer wafLib.WafResultFree(result)
238243

239-
wafTimer.Start()
240-
defer wafTimer.Stop()
241-
242244
// The value of the timeout cannot exceed 2^55
243245
// cf. https://en.cppreference.com/w/cpp/chrono/duration
244246
timeout := uint64(timeBudget.Microseconds()) & 0x008FFFFFFFFFFFFF
245247
ret := wafLib.WafRun(context.cContext, persistentData, ephemeralData, result, timeout)
246248

249+
wafDecodeTimer.Start()
250+
defer wafDecodeTimer.Stop()
251+
247252
return unwrapWafResult(ret, result)
248253
}
249254

encoder.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,6 @@ func (encoder *encoder) Encode(data any) (wo *bindings.WafObject, err error) {
106106
value := reflect.ValueOf(data)
107107
wo = &bindings.WafObject{}
108108

109-
encoder.timer.Start()
110-
defer encoder.timer.Stop()
111109
err = encoder.encode(value, wo, encoder.objectMaxDepth)
112110

113111
if len(encoder.truncations[ObjectTooDeep]) != 0 && !encoder.timer.Exhausted() {
@@ -125,21 +123,6 @@ func (encoder *encoder) Truncations() map[TruncationReason][]int {
125123
return result
126124
}
127125

128-
// EncodeAddresses takes a map of Go values and returns a wafObject pointer and an error.
129-
// The returned wafObject is the root of the tree of nested wafObjects representing the Go values.
130-
// This function is further optimized from Encode to take addresses as input and avoid further
131-
// errors in case the top-level map with addresses as keys is nil.
132-
// Since errors returned by Encode are not sent up between levels of the tree, this means that all errors come from the
133-
// top layer of encoding, which is the map of addresses. Hence, all errors should be developer errors since the map of
134-
// addresses is not user defined custom data.
135-
func (encoder *encoder) EncodeAddresses(addresses map[string]any) (*bindings.WafObject, error) {
136-
if addresses == nil {
137-
return nil, errors.ErrUnsupportedValue
138-
}
139-
140-
return encoder.Encode(addresses)
141-
}
142-
143126
func encodeNative[T native](val T, t bindings.WafObjectType, obj *bindings.WafObject) {
144127
obj.Type = t
145128
obj.Value = (uintptr)(val)

metrics.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,12 @@ type Stats struct {
2525
}
2626

2727
const (
28-
wafPersistentEncoderTag = "_dd.appsec.waf.encode.persistent"
29-
wafEphemeralEncoderTag = "_dd.appsec.waf.encode.ephemeral"
30-
wafRunTag = "_dd.appsec.waf.run"
31-
wafDurationTag = "_dd.appsec.waf.duration"
32-
wafDurationExtTag = "_dd.appsec.waf.duration_ext"
33-
wafTimeoutTag = "_dd.appsec.waf.timeouts"
34-
wafTruncationTag = "_dd.appsec.waf.truncations"
28+
wafEncodeTag = "_dd.appsec.waf.encode"
29+
wafRunTag = "_dd.appsec.waf.duration_ext"
30+
wafDurationTag = "_dd.appsec.waf.duration"
31+
wafDecodeTag = "_dd.appsec.waf.decode"
32+
wafTimeoutTag = "_dd.appsec.waf.timeouts"
33+
wafTruncationTag = "_dd.appsec.waf.truncations"
3534
)
3635

3736
// Metrics transform the stats returned by the WAF into a map of key value metrics for datadog backend

waf_test.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -376,9 +376,8 @@ func TestTimeout(t *testing.T) {
376376
_, err := context.Run(RunAddressData{Persistent: normalValue, Ephemeral: normalValue}, 0)
377377
require.NoError(t, err)
378378
require.NotEmpty(t, context.Stats())
379-
require.NotZero(t, context.Stats().Timers["_dd.appsec.waf.run"])
380-
require.NotZero(t, context.Stats().Timers["_dd.appsec.waf.encode.persistent"])
381-
require.NotZero(t, context.Stats().Timers["_dd.appsec.waf.encode.ephemeral"])
379+
require.NotZero(t, context.Stats().Timers["_dd.appsec.waf.decode"])
380+
require.NotZero(t, context.Stats().Timers["_dd.appsec.waf.encode"])
382381
require.NotZero(t, context.Stats().Timers["_dd.appsec.waf.duration_ext"])
383382
require.NotZero(t, context.Stats().Timers["_dd.appsec.waf.duration"])
384383
})
@@ -390,9 +389,8 @@ func TestTimeout(t *testing.T) {
390389

391390
_, err := context.Run(RunAddressData{Persistent: largeValue}, 0)
392391
require.Equal(t, errors.ErrTimeout, err)
393-
require.GreaterOrEqual(t, context.Stats().Timers["_dd.appsec.waf.run"], time.Millisecond)
394-
require.GreaterOrEqual(t, context.Stats().Timers["_dd.appsec.waf.encode.persistent"], time.Millisecond)
395-
require.Equal(t, context.Stats().Timers["_dd.appsec.waf.encode.ephemeral"], time.Duration(0))
392+
require.GreaterOrEqual(t, context.Stats().Timers["_dd.appsec.waf.duration_ext"], time.Millisecond)
393+
require.GreaterOrEqual(t, context.Stats().Timers["_dd.appsec.waf.encode"], time.Millisecond)
396394
})
397395

398396
t.Run("timeout-ephemeral-encoder", func(t *testing.T) {
@@ -402,9 +400,8 @@ func TestTimeout(t *testing.T) {
402400

403401
_, err := context.Run(RunAddressData{Ephemeral: largeValue}, 0)
404402
require.Equal(t, errors.ErrTimeout, err)
405-
require.GreaterOrEqual(t, context.Stats().Timers["_dd.appsec.waf.run"], time.Millisecond)
406-
require.Equal(t, context.Stats().Timers["_dd.appsec.waf.encode.persistent"], time.Duration(0))
407-
require.GreaterOrEqual(t, context.Stats().Timers["_dd.appsec.waf.encode.ephemeral"], time.Millisecond)
403+
require.GreaterOrEqual(t, context.Stats().Timers["_dd.appsec.waf.duration_ext"], time.Millisecond)
404+
require.GreaterOrEqual(t, context.Stats().Timers["_dd.appsec.waf.encode"], time.Millisecond)
408405
})
409406

410407
t.Run("many-runs", func(t *testing.T) {

0 commit comments

Comments
 (0)