Skip to content

Commit

Permalink
Merge pull request #517 from Countly/boomeranf-device-trace-fix
Browse files Browse the repository at this point in the history
Device trace refactor
  • Loading branch information
turtledreams authored Sep 11, 2024
2 parents eb44e82 + 3db9c23 commit 547e2c9
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 81 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## 24.4.1
- Added types for the SDK
- Added a new method `set_id(newDeviceId)` for managing device id changes according to the device ID Type
- Added a new method `set_id(newDeviceId)` for managing device ID changes according to the device ID Type

- Mitigated an issue that could have prevented automatic Device Traces to not show up in server

## 24.4.0
! Minor breaking change ! For implementations using `salt` the browser compatability is tied to SubtleCrypto's `digest` method support
Expand Down
192 changes: 112 additions & 80 deletions plugin/boomerang/countly_boomerang.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,90 +48,122 @@ Plugin being used - RT, AutoXHR, Continuity, NavigationTiming, ResourceTiming
}
};
var initedBoomr = false;

// device traces need information over multiple beacons. We accumulate the data and send it as a single trace, and then reset it.
var deviceTrace = {};
deviceTrace.type = "device";
deviceTrace.apm_metrics = {};

/**
* Initialize Boomerang
* @param {Object} BOOMR - Boomerang object
*
* @param {Object} beaconData - Boomerang beacon data
*/
function initBoomerang(BOOMR) {
if (BOOMR && !initedBoomr) {
BOOMR.subscribe("before_beacon", function(beaconData) {
self._internals.log("[INFO]", "Boomerang, before_beacon:", JSON.stringify(beaconData, null, 2));
var trace = {};
if (beaconData["rt.start"] !== "manual" && !beaconData["http.initiator"] && beaconData["rt.quit"] !== "") {
trace.type = "device";
trace.apm_metrics = {};
if (typeof beaconData["pt.fp"] !== "undefined") {
trace.apm_metrics.first_paint = beaconData["pt.fp"];
}
else if (typeof beaconData.nt_first_paint !== "undefined") {
trace.apm_metrics.first_paint = beaconData.nt_first_paint - beaconData["rt.tstart"];
}
if (typeof beaconData["pt.fcp"] !== "undefined") {
trace.apm_metrics.first_contentful_paint = beaconData["pt.fcp"];
}
if (typeof beaconData.nt_domint !== "undefined") {
trace.apm_metrics.dom_interactive = beaconData.nt_domint - beaconData["rt.tstart"];
}
if (typeof beaconData.nt_domcontloaded_st !== "undefined" && typeof beaconData.nt_domcontloaded_end !== "undefined") {
trace.apm_metrics.dom_content_loaded_event_end = beaconData.nt_domcontloaded_end - beaconData.nt_domcontloaded_st;
}
if (typeof beaconData.nt_load_st !== "undefined" && typeof beaconData.nt_load_end !== "undefined") {
trace.apm_metrics.load_event_end = beaconData.nt_load_end - beaconData.nt_load_st;
}
if (typeof beaconData["c.fid"] !== "undefined") {
trace.apm_metrics.first_input_delay = beaconData["c.fid"];
}
}
else if (beaconData["http.initiator"] && ["xhr", "spa", "spa_hard"].indexOf(beaconData["http.initiator"]) !== -1) {
var responseTime; var responsePayloadSize; var requestPayloadSize; var
responseCode;
responseTime = beaconData.t_resp;
// t_resp - Time taken from the user initiating the request to the first byte of the response. - Added by RT
responseCode = (typeof beaconData["http.errno"] !== "undefined") ? beaconData["http.errno"] : 200;
function sendDeviceTrace(beaconData) {
self._internals.log("[INFO]", "[Boomerang], collecting device trace info");

try {
var restiming = JSON.parse(beaconData.restiming);
var ResourceTimingDecompression = window.ResourceTimingDecompression;
if (ResourceTimingDecompression && restiming) {
// restiming contains information regarging all the resources that are loaded in any
// spa, spa_hard or xhr requests.
// xhr requests should ideally have only one entry in the array which is the one for
// which the beacon is being sent.
// But for spa_hard requests it can contain multiple entries, one for each resource
// that is loaded in the application. Example - all images, scripts etc.
// ResourceTimingDecompression is not included in the official boomerang library.
ResourceTimingDecompression.HOSTNAMES_REVERSED = false;
var decompressedData = ResourceTimingDecompression.decompressResources(restiming);
var currentBeacon = decompressedData.filter(function(resource) {
return resource.name === beaconData.u;
});
// Currently we rely on this information to appear before the paint information. Otherwise device trace will not include these.
if (typeof beaconData.nt_domint !== "undefined") {
deviceTrace.apm_metrics.dom_interactive = beaconData.nt_domint - beaconData["rt.tstart"];
}
if (typeof beaconData.nt_domcontloaded_st !== "undefined" && typeof beaconData.nt_domcontloaded_end !== "undefined") {
deviceTrace.apm_metrics.dom_content_loaded_event_end = beaconData.nt_domcontloaded_end - beaconData.nt_domcontloaded_st;
}
if (typeof beaconData.nt_load_st !== "undefined" && typeof beaconData.nt_load_end !== "undefined") {
deviceTrace.apm_metrics.load_event_end = beaconData.nt_load_end - beaconData.nt_load_st;
}
if (typeof beaconData["c.fid"] !== "undefined") {
deviceTrace.apm_metrics.first_input_delay = beaconData["c.fid"];
}

if (currentBeacon.length) {
responsePayloadSize = currentBeacon[0].decodedBodySize;
responseTime = currentBeacon[0].duration ? currentBeacon[0].duration : responseTime;
// duration - Returns the difference between the resource's responseEnd timestamp and its startTime timestamp - ResourceTiming API
}
}
}
catch (e) {
self._internals.log("[ERROR]", "Boomerang, Error while using resource timing data decompression", config);
}
// paint timings
if (typeof beaconData["pt.fp"] !== "undefined") {
deviceTrace.apm_metrics.first_paint = beaconData["pt.fp"];
}
else if (typeof beaconData.nt_first_paint !== "undefined") {
deviceTrace.apm_metrics.first_paint = beaconData.nt_first_paint - beaconData["rt.tstart"];
}
if (typeof beaconData["pt.fcp"] !== "undefined") {
deviceTrace.apm_metrics.first_contentful_paint = beaconData["pt.fcp"];
}

trace.type = "network";
trace.apm_metrics = {
response_time: responseTime,
response_payload_size: responsePayloadSize,
request_payload_size: requestPayloadSize,
response_code: responseCode
};
}
// first_paint and first_contentful_paint are the two metrics that are MANDATORY! to send the device traces to the server.
if (deviceTrace.apm_metrics.first_paint && deviceTrace.apm_metrics.first_contentful_paint) {
self._internals.log("[DEBUG]", "[Boomerang], Found all the required metrics to send device trace. Recording the trace.");
deviceTrace.name = (beaconData.u + "").split("//").pop().split("?")[0];
deviceTrace.stz = beaconData["rt.tstart"];
deviceTrace.etz = beaconData["rt.end"];
self.report_trace(deviceTrace);
deviceTrace.apm_metrics = {}; // reset the device trace
}
}

if (trace.type) {
trace.name = (beaconData.u + "").split("//").pop().split("?")[0];
trace.stz = beaconData["rt.tstart"];
trace.etz = beaconData["rt.end"];
self.report_trace(trace);
/**
*
* @param {Object} beaconData - Boomerang beacon data
*/
function sendNetworkTrace(beaconData) {
self._internals.log("[INFO]", "[Boomerang], collecting network trace info");
var trace = {};
if (beaconData["http.initiator"] && ["xhr", "spa", "spa_hard"].indexOf(beaconData["http.initiator"]) !== -1) {
var responseTime; var responsePayloadSize; var requestPayloadSize; var
responseCode;
responseTime = beaconData.t_resp;
// t_resp - Time taken from the user initiating the request to the first byte of the response. - Added by RT
responseCode = (typeof beaconData["http.errno"] !== "undefined") ? beaconData["http.errno"] : 200;

try {
var restiming = JSON.parse(beaconData.restiming);
var ResourceTimingDecompression = window.ResourceTimingDecompression;
if (ResourceTimingDecompression && restiming) {
// restiming contains information regarging all the resources that are loaded in any
// spa, spa_hard or xhr requests.
// xhr requests should ideally have only one entry in the array which is the one for
// which the beacon is being sent.
// But for spa_hard requests it can contain multiple entries, one for each resource
// that is loaded in the application. Example - all images, scripts etc.
// ResourceTimingDecompression is not included in the official boomerang library.
ResourceTimingDecompression.HOSTNAMES_REVERSED = false;
var decompressedData = ResourceTimingDecompression.decompressResources(restiming);
var currentBeacon = decompressedData.filter(function(resource) {
return resource.name === beaconData.u;
});

if (currentBeacon.length) {
responsePayloadSize = currentBeacon[0].decodedBodySize;
responseTime = currentBeacon[0].duration ? currentBeacon[0].duration : responseTime;
// duration - Returns the difference between the resource's responseEnd timestamp and its startTime timestamp - ResourceTiming API
}
}
}
catch (e) {
self._internals.log("[ERROR]", "[Boomerang], Error while using resource timing data decompression", config);
}

trace.type = "network";
trace.apm_metrics = {
response_time: responseTime,
response_payload_size: responsePayloadSize,
request_payload_size: requestPayloadSize,
response_code: responseCode
};
trace.name = (beaconData.u + "").split("//").pop().split("?")[0];
trace.stz = beaconData["rt.tstart"];
trace.etz = beaconData["rt.end"];
self._internals.log("[DEBUG]", "[Boomerang], Found all the required metrics to send network trace. Recording the trace.");
self.report_trace(trace);
}
}

/**
* Initialize Boomerang
* @param {Object} BOOMR - Boomerang object
*/
function initBoomerang(BOOMR) {
if (BOOMR && !initedBoomr) {
BOOMR.subscribe("before_beacon", function(beaconData) {
self._internals.log("[INFO]", "[Boomerang], before_beacon:", JSON.stringify(beaconData, null, 2));
sendDeviceTrace(beaconData);
sendNetworkTrace(beaconData);
});

BOOMR.xhr_excludes = BOOMR.xhr_excludes || {};
Expand All @@ -143,17 +175,17 @@ Plugin being used - RT, AutoXHR, Continuity, NavigationTiming, ResourceTiming
BOOMR.t_end = new Date().getTime();
Countly.BOOMR = BOOMR;
initedBoomr = true;
self._internals.log("[INFO]", "Boomerang initiated:", config);
self._internals.log("[INFO]", "[Boomerang] inited with config:[" + JSON.stringify(config) + "]");
}
else {
self._internals.log("[WARNING]", "Boomerang called without its instance or was already initialized");
self._internals.log("[WARNING]", "[Boomerang] called without its instance or was already initialized");
}
}
if (window.BOOMR) {
initBoomerang(window.BOOMR);
}
else {
self._internals.log("[WARNING]", "Boomerang not yet loaded, waiting for it to load");
self._internals.log("[WARNING]", "[Boomerang] not yet loaded, waiting for it to load");
// Modern browsers
if (document.addEventListener) {
document.addEventListener("onBoomerangLoaded", function(e) {
Expand Down

0 comments on commit 547e2c9

Please sign in to comment.