Skip to content

Commit dbd6500

Browse files
committed
Merge branch 'master' of github.com:PostHog/posthog-js
2 parents e2b76c2 + 09832a3 commit dbd6500

File tree

7 files changed

+169
-12
lines changed

7 files changed

+169
-12
lines changed

src/__tests__/capture-metrics.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { CaptureMetrics } from '../capture-metrics'
2+
3+
import { _ } from '../utils'
4+
5+
jest.mock('../utils')
6+
7+
describe('CaptureMetrics()', () => {
8+
given('captureMetrics', () => new CaptureMetrics(given.enabled, given.capture, given.getTime))
9+
10+
given('enabled', () => true)
11+
given('capture', () => jest.fn())
12+
13+
given('getTime', () => jest.fn())
14+
15+
describe('incr() and decr()', () => {
16+
it('supports incrementing and decrementing metrics', () => {
17+
given.captureMetrics.incr('key')
18+
given.captureMetrics.incr('key2')
19+
given.captureMetrics.incr('key', 3)
20+
given.captureMetrics.decr('key')
21+
22+
expect(given.captureMetrics.metrics).toEqual({ 'phjs-key': 3, 'phjs-key2': 1 })
23+
})
24+
25+
it('does nothing when not enabled', () => {
26+
given('enabled', () => false)
27+
28+
given.captureMetrics.incr('key')
29+
30+
expect(given.captureMetrics.metrics).toEqual({})
31+
})
32+
})
33+
34+
describe('tracking requests', () => {
35+
beforeEach(() => {
36+
let i = 0
37+
_.UUID.mockImplementation(() => i++)
38+
})
39+
40+
it('handles starting and finishing a request', () => {
41+
given.getTime.mockReturnValue(5000)
42+
const id = given.captureMetrics.startRequest({ size: 123 })
43+
44+
given.getTime.mockReturnValue(5100)
45+
const payload = given.captureMetrics.finishRequest(id)
46+
47+
expect(id).toEqual(0)
48+
expect(payload).toEqual({ size: 123, duration: 100 })
49+
})
50+
51+
it('handles marking a request as failed', () => {
52+
given.captureMetrics.markRequestFailed({ foo: 'bar' })
53+
54+
expect(given.capture).toHaveBeenCalledWith('$capture_failed_request', { foo: 'bar' })
55+
})
56+
57+
it('handles marking all in-flight requests as failed', () => {
58+
given.getTime.mockReturnValue(5000)
59+
given.captureMetrics.startRequest({ size: 100 })
60+
61+
given.getTime.mockReturnValue(5100)
62+
given.captureMetrics.startRequest({ size: 200 })
63+
64+
given.getTime.mockReturnValue(5500)
65+
66+
given.captureMetrics.captureInProgressRequests()
67+
68+
expect(given.capture).toHaveBeenCalledTimes(2)
69+
expect(given.capture).toHaveBeenCalledWith('$capture_failed_request', {
70+
size: 100,
71+
duration: 500,
72+
type: 'inflight_at_unload',
73+
})
74+
expect(given.capture).toHaveBeenCalledWith('$capture_failed_request', {
75+
size: 200,
76+
duration: 400,
77+
type: 'inflight_at_unload',
78+
})
79+
})
80+
81+
it('does nothing if not enabled', () => {
82+
given('enabled', () => false)
83+
84+
given.captureMetrics.startRequest({ size: 100 })
85+
given.captureMetrics.captureInProgressRequests()
86+
given.captureMetrics.markRequestFailed({ foo: 'bar' })
87+
given.captureMetrics.finishRequest(null)
88+
89+
expect(given.capture).not.toHaveBeenCalled()
90+
})
91+
})
92+
})

src/__tests__/extensions/sessionrecording.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ describe('SessionRecording', () => {
9999
compression: 'lz64',
100100
_noTruncate: true,
101101
_batchKey: 'sessionRecording',
102+
_metrics: expect.anything(),
102103
}
103104
)
104105
expect(given.posthog.capture).toHaveBeenCalledWith(
@@ -114,6 +115,7 @@ describe('SessionRecording', () => {
114115
compression: 'lz64',
115116
_noTruncate: true,
116117
_batchKey: 'sessionRecording',
118+
_metrics: expect.anything(),
117119
}
118120
)
119121
})

src/__tests__/posthog-core.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PostHogLib } from '../posthog-core'
2+
import { CaptureMetrics } from '../capture-metrics'
23
import { _ } from '../utils'
34

45
given('lib', () => Object.assign(new PostHogLib(), given.overrides))
@@ -111,9 +112,7 @@ describe('capture()', () => {
111112
properties: jest.fn(),
112113
},
113114
compression: {},
114-
_captureMetrics: {
115-
incr: jest.fn(),
116-
},
115+
_captureMetrics: new CaptureMetrics(),
117116
__captureHooks: [],
118117
}))
119118

src/capture-metrics.js

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,59 @@
1+
import { _ } from './utils'
2+
13
export class CaptureMetrics {
2-
constructor(capture) {
4+
constructor(enabled, capture, getTime = () => new Date().getTime()) {
5+
this.enabled = enabled
36
this.capture = capture
7+
this.getTime = getTime
48
this.metrics = {}
9+
this.requests = {}
510
}
611

712
incr(key, by = 1) {
8-
if (this.capture) {
13+
if (this.enabled) {
914
key = `phjs-${key}`
1015
this.metrics[key] = (this.metrics[key] || 0) + by
1116
}
1217
}
1318

1419
decr(key) {
15-
if (this.capture) {
20+
if (this.enabled) {
1621
key = `phjs-${key}`
1722
this.metrics[key] = (this.metrics[key] || 0) - 1
1823
}
1924
}
25+
26+
startRequest(payload) {
27+
if (this.enabled) {
28+
const requestId = _.UUID()
29+
30+
this.requests[requestId] = [this.getTime(), payload]
31+
32+
return requestId
33+
}
34+
}
35+
36+
finishRequest(requestId) {
37+
if (this.enabled) {
38+
const [startTime, payload] = this.requests[requestId]
39+
payload['duration'] = this.getTime() - startTime
40+
delete this.requests[requestId]
41+
return payload
42+
}
43+
}
44+
45+
markRequestFailed(payload) {
46+
if (this.enabled) {
47+
this.capture('$capture_failed_request', payload)
48+
}
49+
}
50+
51+
captureInProgressRequests() {
52+
if (this.enabled) {
53+
Object.keys(this.requests).forEach((requestId) => {
54+
const payload = this.finishRequest(requestId)
55+
this.markRequestFailed({ ...payload, type: 'inflight_at_unload' })
56+
})
57+
}
58+
}
2059
}

src/extensions/sessionrecording.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ export class SessionRecording {
8888
compression: 'lz64', // Force lz64 even if /decide endpoint has not yet responded
8989
_noTruncate: true,
9090
_batchKey: 'sessionRecording',
91+
_metrics: {
92+
rrweb_full_snapshot: properties.$snapshot_data.type === 2,
93+
},
9194
})
9295
}
9396
}

src/posthog-core.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ PostHogLib.prototype._init = function (token, config, name) {
215215

216216
this['_jsc'] = function () {}
217217

218-
this._captureMetrics = new CaptureMetrics(this.get_config('_capture_metrics'))
218+
this._captureMetrics = new CaptureMetrics(this.get_config('_capture_metrics'), _.bind(this.capture, this))
219+
219220
this._requestQueue = new RequestQueue(this._captureMetrics, _.bind(this._handle_queued_event, this))
220221
this.__captureHooks = []
221222
this.__request_queue = []
@@ -326,6 +327,7 @@ PostHogLib.prototype._handle_unload = function () {
326327
if (this.get_config('_capture_metrics')) {
327328
this._requestQueue.updateUnloadMetrics()
328329
this.capture('$capture_metrics', this._captureMetrics.metrics)
330+
this._captureMetrics.captureInProgressRequests()
329331
}
330332
this._requestQueue.unload()
331333
}
@@ -368,12 +370,15 @@ PostHogLib.prototype._send_request = function (url, data, options, callback) {
368370
var useSendBeacon = window.navigator.sendBeacon && options.transport.toLowerCase() === 'sendbeacon'
369371
var use_post = useSendBeacon || options.method === 'POST'
370372

371-
const classifier = data.length > 1000 ? 'large' : 'small'
372373
this._captureMetrics.incr('_send_request')
373-
this._captureMetrics.incr(`_send_request_${options.transport}`)
374-
this._captureMetrics.incr(`_send_request_${classifier}`)
375374
this._captureMetrics.incr('_send_request_inflight')
376375

376+
const requestId = this._captureMetrics.startRequest({
377+
size: data.length,
378+
endpoint: url.slice(url.length - 2),
379+
...options._metrics,
380+
})
381+
377382
// needed to correctly format responses
378383
var verbose_mode = this.get_config('verbose')
379384
if (data['verbose']) {
@@ -451,8 +456,10 @@ PostHogLib.prototype._send_request = function (url, data, options, callback) {
451456
if (req.readyState === 4) {
452457
this._captureMetrics.incr(`xhr-response`)
453458
this._captureMetrics.incr(`xhr-response-${req.status}`)
454-
this._captureMetrics.incr(`xhr-done-${classifier}-${req.status}`)
455459
this._captureMetrics.decr('_send_request_inflight')
460+
461+
const data = this._captureMetrics.finishRequest(requestId)
462+
456463
// XMLHttpRequest.DONE == 4, except in safari 4
457464
if (req.status === 200) {
458465
if (callback) {
@@ -468,6 +475,14 @@ PostHogLib.prototype._send_request = function (url, data, options, callback) {
468475
} else {
469476
var error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText
470477
console.error(error)
478+
479+
this._captureMetrics.markRequestFailed({
480+
...data,
481+
type: 'non_200',
482+
status: req.status,
483+
statusText: req.statusText,
484+
})
485+
471486
if (callback) {
472487
if (verbose_mode) {
473488
callback({ status: 0, error: error })

src/request-queue.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,14 @@ export class RequestQueue {
9898
_.each(this._event_queue, (request) => {
9999
const { url, data, options } = request
100100
const key = (options ? options._batchKey : null) || url
101-
if (requests[key] === undefined) requests[key] = { data: [], url, options }
101+
if (requests[key] === undefined) {
102+
requests[key] = { data: [], url, options }
103+
}
104+
105+
// :TRICKY: Metrics-only code
106+
if (options && requests[key].options && requests[key].options._metrics) {
107+
requests[key].options._metrics['rrweb_full_snapshot'] ||= options._metrics['rrweb_full_snapshot']
108+
}
102109
requests[key].data.push(data)
103110
})
104111
return requests

0 commit comments

Comments
 (0)