Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add page.on('metric') #1456

Merged
merged 49 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
f4aeae8
Refactor page.on to work with any
ankur22 Oct 2, 2024
09d83fd
Refactor page.on to allow for other event types
ankur22 Oct 2, 2024
7cfdef6
Refactor event so that it is exported
ankur22 Oct 2, 2024
6c004d3
Refactor page mapping to work with switch
ankur22 Oct 2, 2024
aaa58f6
Add default case for page.on in mapping
ankur22 Oct 2, 2024
ccb8f67
Add metric event
ankur22 Oct 2, 2024
87f0a33
Add exportedMetric
ankur22 Oct 2, 2024
2098578
Add URLGroups type
ankur22 Oct 2, 2024
2b6fc3d
Add metric mapping
ankur22 Oct 2, 2024
99ee86e
Add a grouping func to sobek runtime
ankur22 Oct 2, 2024
085f8b2
Add a callback to GroupURLTag
ankur22 Oct 2, 2024
44f3592
Use the callback to perform the matching test
ankur22 Oct 2, 2024
cf04f0e
Add synchronization and name propagation
ankur22 Oct 2, 2024
7b08ef4
Add function to call the handlers
ankur22 Oct 2, 2024
13fb9d5
Add call URLGroupingName before emit of metrics
ankur22 Oct 2, 2024
11e5cb6
Fix network_manager_test.go test
ankur22 Oct 3, 2024
d7fb59e
Update web vital metric to retrieve name
ankur22 Oct 3, 2024
0204274
Add a test for page.on('metric')
ankur22 Oct 3, 2024
2b281ee
Fix lint issue
ankur22 Oct 3, 2024
8a66105
Add an example for page.on('metric')
ankur22 Oct 3, 2024
373caa0
Rename urlGroupingName to unexport it
ankur22 Oct 3, 2024
ca74548
Update URL and use any instead of interface{}
ankur22 Oct 4, 2024
669d64f
Refactor to use atomic in TestPageOnMetric
ankur22 Oct 4, 2024
0a347d2
Fix name with strings.TrimSpace
ankur22 Oct 4, 2024
3c2358f
Refactor url tagging into its own function
ankur22 Oct 4, 2024
8160a1c
Refactor mapMsgAndHandleEvent out
ankur22 Oct 4, 2024
beadd84
Refactor how the name is returned back to caller
ankur22 Oct 4, 2024
8d6da91
Add summary for urlGroupingName
ankur22 Oct 4, 2024
300d543
Remove a couple of locks and add a test
ankur22 Oct 4, 2024
eb8e345
Add comments on test
ankur22 Oct 4, 2024
c81812b
Rename groups to urls
ankur22 Oct 4, 2024
4a8211f
Add regexCallback
ankur22 Oct 4, 2024
f1f9c67
Rename groupURLTag to Tag
ankur22 Oct 4, 2024
e30e9e2
Rename msg to metric
ankur22 Oct 4, 2024
4ad926a
Rename urlGroupingName to urlTagName
ankur22 Oct 4, 2024
1f8ebb3
Rename to MetricEvent from ExportedMetric
ankur22 Oct 4, 2024
823d5a7
Refactor MetricEvent out of loop
ankur22 Oct 7, 2024
192abfa
Rename URLGroups to URLOverrides
ankur22 Oct 7, 2024
910c63c
Rename type and fields
ankur22 Oct 7, 2024
45b6cd5
Rename name and nameChanged
ankur22 Oct 7, 2024
044cc03
Rename to _k6BrowserCheckRegEx
ankur22 Oct 7, 2024
3c1ca54
Rename func type to k6BrowserCheckRegEx
ankur22 Oct 7, 2024
8fd880a
Rename to url from urlTag
ankur22 Oct 7, 2024
e6d3c39
Rename val to matched
ankur22 Oct 7, 2024
826b527
Rename name to userProvidedTagName
ankur22 Oct 7, 2024
a42e4e7
Use a bool to signal a match was found
ankur22 Oct 7, 2024
4d9fa0c
Update args onto new lines
ankur22 Oct 7, 2024
bcc15bc
Update test with t.Parallel()
ankur22 Oct 7, 2024
a6f9e37
Rename the JS fields back to url and name
ankur22 Oct 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions browser/metric_event_mapping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package browser

import (
"fmt"

"github.com/grafana/xk6-browser/common"
)

// mapMetricEvent to the JS module.
func mapMetricEvent(vu moduleVU, cm *common.MetricEvent) (mapping, error) {
rt := vu.VU.Runtime()

// We're setting up the function in the Sobek context that will be reused
// for this VU.
_, err := rt.RunString(`
function _k6BrowserCheckRegEx(pattern, url) {
let r = pattern;
if (typeof pattern === 'string') {
r = new RegExp(pattern);
}
return r.test(url);
}`)
if err != nil {
return nil, fmt.Errorf("evaluating regex function: %w", err)
}

return mapping{
"Tag": func(urls common.URLTagPatterns) error {
callback := func(pattern, url string) (bool, error) {
inancgumus marked this conversation as resolved.
Show resolved Hide resolved
js := fmt.Sprintf(`_k6BrowserCheckRegEx(%s, '%s')`, pattern, url)

matched, err := rt.RunString(js)
if err != nil {
return false, fmt.Errorf("matching url with regex: %w", err)
}

return matched.ToBoolean(), nil
}

return cm.Tag(callback, urls)
},
}, nil
}
63 changes: 51 additions & 12 deletions browser/page_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package browser

import (
"context"
"errors"
"fmt"
"time"

Expand Down Expand Up @@ -206,18 +207,56 @@ func mapPage(vu moduleVU, p *common.Page) mapping { //nolint:gocognit,cyclop
"on": func(event string, handler sobek.Callable) error {
tq := vu.taskQueueRegistry.get(vu.Context(), p.TargetID())

mapMsgAndHandleEvent := func(m *common.ConsoleMessage) error {
mapping := mapConsoleMessage(vu, m)
_, err := handler(sobek.Undefined(), vu.Runtime().ToValue(mapping))
return err
}
runInTaskQueue := func(m *common.ConsoleMessage) {
tq.Queue(func() error {
if err := mapMsgAndHandleEvent(m); err != nil {
return fmt.Errorf("executing page.on handler: %w", err)
}
return nil
})
var runInTaskQueue func(any)
ankur22 marked this conversation as resolved.
Show resolved Hide resolved
inancgumus marked this conversation as resolved.
Show resolved Hide resolved
switch event {
case common.EventPageConsoleAPICalled:
ankur22 marked this conversation as resolved.
Show resolved Hide resolved
ankur22 marked this conversation as resolved.
Show resolved Hide resolved
mapMsgAndHandleEvent := func(m *common.ConsoleMessage) error {
mapping := mapConsoleMessage(vu, m)
_, err := handler(sobek.Undefined(), vu.VU.Runtime().ToValue(mapping))
return err
}
runInTaskQueue = func(a any) {
tq.Queue(func() error {
m, ok := a.(*common.ConsoleMessage)
ankur22 marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
return errors.New("incorrect console message")
}

if err := mapMsgAndHandleEvent(m); err != nil {
return fmt.Errorf("executing page.on handler: %w", err)
}
return nil
})
}
case common.EventPageMetricCalled:
runInTaskQueue = func(a any) {
// The function on the taskqueue runs in its own goroutine
// so we need to use a channel to wait for it to complete
// since we're waiting for updates from the handler which
// will be written to the ExportedMetric.
c := make(chan bool)
tq.Queue(func() error {
defer close(c)

m, ok := a.(*common.MetricEvent)
if !ok {
return errors.New("incorrect metric message")
ankur22 marked this conversation as resolved.
Show resolved Hide resolved
}

mapping, err := mapMetricEvent(vu, m)
if err != nil {
return fmt.Errorf("mapping the metric: %w", err)
}

if _, err = handler(sobek.Undefined(), vu.VU.Runtime().ToValue(mapping)); err != nil {
return fmt.Errorf("executing page.on('metric') handler: %w", err)
}
return nil
})
<-c
}
default:
return fmt.Errorf("unknown page event: %q", event)
ankur22 marked this conversation as resolved.
Show resolved Hide resolved
}

return p.On(event, runInTaskQueue) //nolint:wrapcheck
Expand Down
8 changes: 7 additions & 1 deletion browser/sync_page_mapping.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package browser

import (
"errors"
"fmt"

"github.com/grafana/sobek"
Expand Down Expand Up @@ -121,8 +122,13 @@ func syncMapPage(vu moduleVU, p *common.Page) mapping { //nolint:gocognit,cyclop
_, err := handler(sobek.Undefined(), vu.Runtime().ToValue(mapping))
return err
}
runInTaskQueue := func(m *common.ConsoleMessage) {
runInTaskQueue := func(a any) {
ankur22 marked this conversation as resolved.
Show resolved Hide resolved
tq.Queue(func() error {
m, ok := a.(*common.ConsoleMessage)
if !ok {
return errors.New("incorrect message")
}

if err := mapMsgAndHandleEvent(m); err != nil {
return fmt.Errorf("executing page.on handler: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions common/frame_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func NewFrameSession(
if fs.parent != nil {
parentNM = fs.parent.networkManager
}
fs.networkManager, err = NewNetworkManager(ctx, k6Metrics, s, fs.manager, parentNM)
fs.networkManager, err = NewNetworkManager(ctx, k6Metrics, s, fs.manager, parentNM, fs.manager.page)
if err != nil {
l.Debugf("NewFrameSession:NewNetworkManager", "sid:%v tid:%v err:%v",
s.ID(), tid, err)
Expand Down Expand Up @@ -355,7 +355,7 @@ func (fs *FrameSession) parseAndEmitWebVitalMetric(object string) error {
state := fs.vu.State()
tags := state.Tags.GetCurrentValues().Tags
if state.Options.SystemTags.Has(k6metrics.TagURL) {
tags = tags.With("url", wv.URL)
tags = handleURLTag(fs.ctx, fs.page, wv.URL, tags)
}

tags = tags.With("rating", wv.Rating)
Expand Down
33 changes: 30 additions & 3 deletions common/network_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ func (c *Credentials) Parse(ctx context.Context, credentials sobek.Value) error
return nil
}

type metricInterceptor interface {
urlTagName(ctx context.Context, urlTag string) (string, bool)
}
inancgumus marked this conversation as resolved.
Show resolved Hide resolved

// NetworkManager manages all frames in HTML document.
type NetworkManager struct {
BaseEventEmitter
Expand All @@ -70,6 +74,7 @@ type NetworkManager struct {
resolver k6netext.Resolver
vu k6modules.VU
customMetrics *k6ext.CustomMetrics
mi metricInterceptor

// TODO: manage inflight requests separately (move them between the two maps
// as they transition from inflight -> completed)
Expand All @@ -88,7 +93,12 @@ type NetworkManager struct {

// NewNetworkManager creates a new network manager.
func NewNetworkManager(
ctx context.Context, customMetrics *k6ext.CustomMetrics, s session, fm *FrameManager, parent *NetworkManager,
ctx context.Context,
customMetrics *k6ext.CustomMetrics,
s session,
fm *FrameManager,
parent *NetworkManager,
mi metricInterceptor,
) (*NetworkManager, error) {
vu := k6ext.GetVU(ctx)
state := vu.State()
Expand All @@ -114,6 +124,7 @@ func NewNetworkManager(
attemptedAuth: make(map[fetch.RequestID]bool),
extraHTTPHeaders: make(map[string]string),
networkProfile: NewNetworkProfile(),
mi: mi,
}
m.initEvents()
if err := m.initDomains(); err != nil {
Expand Down Expand Up @@ -181,7 +192,7 @@ func (m *NetworkManager) emitRequestMetrics(req *Request) {
tags = tags.With("method", req.method)
}
if state.Options.SystemTags.Has(k6metrics.TagURL) {
tags = tags.With("url", req.URL())
tags = handleURLTag(m.vu.Context(), m.mi, req.URL(), tags)
}

k6metrics.PushIfNotDone(m.vu.Context(), state.Samples, k6metrics.ConnectedSamples{
Expand Down Expand Up @@ -234,7 +245,7 @@ func (m *NetworkManager) emitResponseMetrics(resp *Response, req *Request) {
tags = tags.With("method", req.method)
}
if state.Options.SystemTags.Has(k6metrics.TagURL) {
tags = tags.With("url", url)
tags = handleURLTag(m.vu.Context(), m.mi, url, tags)
}
if state.Options.SystemTags.Has(k6metrics.TagIP) {
tags = tags.With("ip", ipAddress)
Expand Down Expand Up @@ -278,6 +289,22 @@ func (m *NetworkManager) emitResponseMetrics(resp *Response, req *Request) {
}
}

// handleURLTag will check if the url tag needs to be grouped by testing
// against user supplied regex. If there's a match a user supplied name will
// be used instead of the url for the url tag, otherwise the url will be used.
func handleURLTag(ctx context.Context, mi metricInterceptor, url string, tags *k6metrics.TagSet) *k6metrics.TagSet {
if newTagName, urlMatched := mi.urlTagName(ctx, url); urlMatched {
tags = tags.With("url", newTagName)
tags = tags.With("name", newTagName)
return tags
}

tags = tags.With("url", url)
tags = tags.With("name", url)

return tags
}

func (m *NetworkManager) handleRequestRedirect(req *Request, redirectResponse *network.Response, timestamp *cdp.MonotonicTime) {
resp := NewHTTPResponse(m.ctx, req, redirectResponse, timestamp)
req.responseMu.Lock()
Expand Down
8 changes: 7 additions & 1 deletion common/network_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ func TestOnRequestPausedBlockedIPs(t *testing.T) {
}
}

type MetricInterceptorMock struct{}

func (m *MetricInterceptorMock) urlTagName(ctx context.Context, urlTag string) (string, bool) {
return "", false
}
inancgumus marked this conversation as resolved.
Show resolved Hide resolved

func TestNetworkManagerEmitRequestResponseMetricsTimingSkew(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -271,7 +277,7 @@ func TestNetworkManagerEmitRequestResponseMetricsTimingSkew(t *testing.T) {

var (
vu = k6test.NewVU(t)
nm = &NetworkManager{ctx: vu.Context(), vu: vu, customMetrics: k6m}
nm = &NetworkManager{ctx: vu.Context(), vu: vu, customMetrics: k6m, mi: &MetricInterceptorMock{}}
)
vu.ActivateVU()

Expand Down
Loading
Loading