Skip to content

Commit 547e2c9

Browse files
authored
Merge pull request #517 from Countly/boomeranf-device-trace-fix
Device trace refactor
2 parents eb44e82 + 3db9c23 commit 547e2c9

File tree

2 files changed

+115
-81
lines changed

2 files changed

+115
-81
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## 24.4.1
22
- Added types for the SDK
3-
- Added a new method `set_id(newDeviceId)` for managing device id changes according to the device ID Type
3+
- Added a new method `set_id(newDeviceId)` for managing device ID changes according to the device ID Type
4+
5+
- Mitigated an issue that could have prevented automatic Device Traces to not show up in server
46

57
## 24.4.0
68
! Minor breaking change ! For implementations using `salt` the browser compatability is tied to SubtleCrypto's `digest` method support

plugin/boomerang/countly_boomerang.js

Lines changed: 112 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -48,90 +48,122 @@ Plugin being used - RT, AutoXHR, Continuity, NavigationTiming, ResourceTiming
4848
}
4949
};
5050
var initedBoomr = false;
51+
52+
// device traces need information over multiple beacons. We accumulate the data and send it as a single trace, and then reset it.
53+
var deviceTrace = {};
54+
deviceTrace.type = "device";
55+
deviceTrace.apm_metrics = {};
56+
5157
/**
52-
* Initialize Boomerang
53-
* @param {Object} BOOMR - Boomerang object
58+
*
59+
* @param {Object} beaconData - Boomerang beacon data
5460
*/
55-
function initBoomerang(BOOMR) {
56-
if (BOOMR && !initedBoomr) {
57-
BOOMR.subscribe("before_beacon", function(beaconData) {
58-
self._internals.log("[INFO]", "Boomerang, before_beacon:", JSON.stringify(beaconData, null, 2));
59-
var trace = {};
60-
if (beaconData["rt.start"] !== "manual" && !beaconData["http.initiator"] && beaconData["rt.quit"] !== "") {
61-
trace.type = "device";
62-
trace.apm_metrics = {};
63-
if (typeof beaconData["pt.fp"] !== "undefined") {
64-
trace.apm_metrics.first_paint = beaconData["pt.fp"];
65-
}
66-
else if (typeof beaconData.nt_first_paint !== "undefined") {
67-
trace.apm_metrics.first_paint = beaconData.nt_first_paint - beaconData["rt.tstart"];
68-
}
69-
if (typeof beaconData["pt.fcp"] !== "undefined") {
70-
trace.apm_metrics.first_contentful_paint = beaconData["pt.fcp"];
71-
}
72-
if (typeof beaconData.nt_domint !== "undefined") {
73-
trace.apm_metrics.dom_interactive = beaconData.nt_domint - beaconData["rt.tstart"];
74-
}
75-
if (typeof beaconData.nt_domcontloaded_st !== "undefined" && typeof beaconData.nt_domcontloaded_end !== "undefined") {
76-
trace.apm_metrics.dom_content_loaded_event_end = beaconData.nt_domcontloaded_end - beaconData.nt_domcontloaded_st;
77-
}
78-
if (typeof beaconData.nt_load_st !== "undefined" && typeof beaconData.nt_load_end !== "undefined") {
79-
trace.apm_metrics.load_event_end = beaconData.nt_load_end - beaconData.nt_load_st;
80-
}
81-
if (typeof beaconData["c.fid"] !== "undefined") {
82-
trace.apm_metrics.first_input_delay = beaconData["c.fid"];
83-
}
84-
}
85-
else if (beaconData["http.initiator"] && ["xhr", "spa", "spa_hard"].indexOf(beaconData["http.initiator"]) !== -1) {
86-
var responseTime; var responsePayloadSize; var requestPayloadSize; var
87-
responseCode;
88-
responseTime = beaconData.t_resp;
89-
// t_resp - Time taken from the user initiating the request to the first byte of the response. - Added by RT
90-
responseCode = (typeof beaconData["http.errno"] !== "undefined") ? beaconData["http.errno"] : 200;
61+
function sendDeviceTrace(beaconData) {
62+
self._internals.log("[INFO]", "[Boomerang], collecting device trace info");
9163

92-
try {
93-
var restiming = JSON.parse(beaconData.restiming);
94-
var ResourceTimingDecompression = window.ResourceTimingDecompression;
95-
if (ResourceTimingDecompression && restiming) {
96-
// restiming contains information regarging all the resources that are loaded in any
97-
// spa, spa_hard or xhr requests.
98-
// xhr requests should ideally have only one entry in the array which is the one for
99-
// which the beacon is being sent.
100-
// But for spa_hard requests it can contain multiple entries, one for each resource
101-
// that is loaded in the application. Example - all images, scripts etc.
102-
// ResourceTimingDecompression is not included in the official boomerang library.
103-
ResourceTimingDecompression.HOSTNAMES_REVERSED = false;
104-
var decompressedData = ResourceTimingDecompression.decompressResources(restiming);
105-
var currentBeacon = decompressedData.filter(function(resource) {
106-
return resource.name === beaconData.u;
107-
});
64+
// Currently we rely on this information to appear before the paint information. Otherwise device trace will not include these.
65+
if (typeof beaconData.nt_domint !== "undefined") {
66+
deviceTrace.apm_metrics.dom_interactive = beaconData.nt_domint - beaconData["rt.tstart"];
67+
}
68+
if (typeof beaconData.nt_domcontloaded_st !== "undefined" && typeof beaconData.nt_domcontloaded_end !== "undefined") {
69+
deviceTrace.apm_metrics.dom_content_loaded_event_end = beaconData.nt_domcontloaded_end - beaconData.nt_domcontloaded_st;
70+
}
71+
if (typeof beaconData.nt_load_st !== "undefined" && typeof beaconData.nt_load_end !== "undefined") {
72+
deviceTrace.apm_metrics.load_event_end = beaconData.nt_load_end - beaconData.nt_load_st;
73+
}
74+
if (typeof beaconData["c.fid"] !== "undefined") {
75+
deviceTrace.apm_metrics.first_input_delay = beaconData["c.fid"];
76+
}
10877

109-
if (currentBeacon.length) {
110-
responsePayloadSize = currentBeacon[0].decodedBodySize;
111-
responseTime = currentBeacon[0].duration ? currentBeacon[0].duration : responseTime;
112-
// duration - Returns the difference between the resource's responseEnd timestamp and its startTime timestamp - ResourceTiming API
113-
}
114-
}
115-
}
116-
catch (e) {
117-
self._internals.log("[ERROR]", "Boomerang, Error while using resource timing data decompression", config);
118-
}
78+
// paint timings
79+
if (typeof beaconData["pt.fp"] !== "undefined") {
80+
deviceTrace.apm_metrics.first_paint = beaconData["pt.fp"];
81+
}
82+
else if (typeof beaconData.nt_first_paint !== "undefined") {
83+
deviceTrace.apm_metrics.first_paint = beaconData.nt_first_paint - beaconData["rt.tstart"];
84+
}
85+
if (typeof beaconData["pt.fcp"] !== "undefined") {
86+
deviceTrace.apm_metrics.first_contentful_paint = beaconData["pt.fcp"];
87+
}
11988

120-
trace.type = "network";
121-
trace.apm_metrics = {
122-
response_time: responseTime,
123-
response_payload_size: responsePayloadSize,
124-
request_payload_size: requestPayloadSize,
125-
response_code: responseCode
126-
};
127-
}
89+
// first_paint and first_contentful_paint are the two metrics that are MANDATORY! to send the device traces to the server.
90+
if (deviceTrace.apm_metrics.first_paint && deviceTrace.apm_metrics.first_contentful_paint) {
91+
self._internals.log("[DEBUG]", "[Boomerang], Found all the required metrics to send device trace. Recording the trace.");
92+
deviceTrace.name = (beaconData.u + "").split("//").pop().split("?")[0];
93+
deviceTrace.stz = beaconData["rt.tstart"];
94+
deviceTrace.etz = beaconData["rt.end"];
95+
self.report_trace(deviceTrace);
96+
deviceTrace.apm_metrics = {}; // reset the device trace
97+
}
98+
}
12899

129-
if (trace.type) {
130-
trace.name = (beaconData.u + "").split("//").pop().split("?")[0];
131-
trace.stz = beaconData["rt.tstart"];
132-
trace.etz = beaconData["rt.end"];
133-
self.report_trace(trace);
100+
/**
101+
*
102+
* @param {Object} beaconData - Boomerang beacon data
103+
*/
104+
function sendNetworkTrace(beaconData) {
105+
self._internals.log("[INFO]", "[Boomerang], collecting network trace info");
106+
var trace = {};
107+
if (beaconData["http.initiator"] && ["xhr", "spa", "spa_hard"].indexOf(beaconData["http.initiator"]) !== -1) {
108+
var responseTime; var responsePayloadSize; var requestPayloadSize; var
109+
responseCode;
110+
responseTime = beaconData.t_resp;
111+
// t_resp - Time taken from the user initiating the request to the first byte of the response. - Added by RT
112+
responseCode = (typeof beaconData["http.errno"] !== "undefined") ? beaconData["http.errno"] : 200;
113+
114+
try {
115+
var restiming = JSON.parse(beaconData.restiming);
116+
var ResourceTimingDecompression = window.ResourceTimingDecompression;
117+
if (ResourceTimingDecompression && restiming) {
118+
// restiming contains information regarging all the resources that are loaded in any
119+
// spa, spa_hard or xhr requests.
120+
// xhr requests should ideally have only one entry in the array which is the one for
121+
// which the beacon is being sent.
122+
// But for spa_hard requests it can contain multiple entries, one for each resource
123+
// that is loaded in the application. Example - all images, scripts etc.
124+
// ResourceTimingDecompression is not included in the official boomerang library.
125+
ResourceTimingDecompression.HOSTNAMES_REVERSED = false;
126+
var decompressedData = ResourceTimingDecompression.decompressResources(restiming);
127+
var currentBeacon = decompressedData.filter(function(resource) {
128+
return resource.name === beaconData.u;
129+
});
130+
131+
if (currentBeacon.length) {
132+
responsePayloadSize = currentBeacon[0].decodedBodySize;
133+
responseTime = currentBeacon[0].duration ? currentBeacon[0].duration : responseTime;
134+
// duration - Returns the difference between the resource's responseEnd timestamp and its startTime timestamp - ResourceTiming API
135+
}
134136
}
137+
}
138+
catch (e) {
139+
self._internals.log("[ERROR]", "[Boomerang], Error while using resource timing data decompression", config);
140+
}
141+
142+
trace.type = "network";
143+
trace.apm_metrics = {
144+
response_time: responseTime,
145+
response_payload_size: responsePayloadSize,
146+
request_payload_size: requestPayloadSize,
147+
response_code: responseCode
148+
};
149+
trace.name = (beaconData.u + "").split("//").pop().split("?")[0];
150+
trace.stz = beaconData["rt.tstart"];
151+
trace.etz = beaconData["rt.end"];
152+
self._internals.log("[DEBUG]", "[Boomerang], Found all the required metrics to send network trace. Recording the trace.");
153+
self.report_trace(trace);
154+
}
155+
}
156+
157+
/**
158+
* Initialize Boomerang
159+
* @param {Object} BOOMR - Boomerang object
160+
*/
161+
function initBoomerang(BOOMR) {
162+
if (BOOMR && !initedBoomr) {
163+
BOOMR.subscribe("before_beacon", function(beaconData) {
164+
self._internals.log("[INFO]", "[Boomerang], before_beacon:", JSON.stringify(beaconData, null, 2));
165+
sendDeviceTrace(beaconData);
166+
sendNetworkTrace(beaconData);
135167
});
136168

137169
BOOMR.xhr_excludes = BOOMR.xhr_excludes || {};
@@ -143,17 +175,17 @@ Plugin being used - RT, AutoXHR, Continuity, NavigationTiming, ResourceTiming
143175
BOOMR.t_end = new Date().getTime();
144176
Countly.BOOMR = BOOMR;
145177
initedBoomr = true;
146-
self._internals.log("[INFO]", "Boomerang initiated:", config);
178+
self._internals.log("[INFO]", "[Boomerang] inited with config:[" + JSON.stringify(config) + "]");
147179
}
148180
else {
149-
self._internals.log("[WARNING]", "Boomerang called without its instance or was already initialized");
181+
self._internals.log("[WARNING]", "[Boomerang] called without its instance or was already initialized");
150182
}
151183
}
152184
if (window.BOOMR) {
153185
initBoomerang(window.BOOMR);
154186
}
155187
else {
156-
self._internals.log("[WARNING]", "Boomerang not yet loaded, waiting for it to load");
188+
self._internals.log("[WARNING]", "[Boomerang] not yet loaded, waiting for it to load");
157189
// Modern browsers
158190
if (document.addEventListener) {
159191
document.addEventListener("onBoomerangLoaded", function(e) {

0 commit comments

Comments
 (0)