Repository: mozilla/pdf.js
Pull request #19856: Don't update the visible canvas at 60 fps (bug 1936605)
Author: login: calixteman
Labels: name: viewer
name: performance
Created: 2025-04-23T19:40:00Z | Merged: 2025-04-29T20:31:03Z

Instead, we update the visible canvas every 500ms. With large canvas, updating at 60fps lead to a lot gfx transactions and it can take a lot of time. For example, with wuppertal_2012.pdf on Windows, displaying it at 150% takes around 14 min !!! without this patch when it takes only around 14 sec with. Even at 30% it helps to improve the performance by around 20%.


Diff:
diff --git a/web/app.js b/web/app.js
index 1e245ec386166..39f3a00716313 100644
--- a/web/app.js
+++ b/web/app.js
@@ -326,8 +326,6 @@ const PDFViewerApplication = {
       }
     }
     if (params.has("pdfbug")) {
-      AppOptions.setAll({ pdfBug: true, fontExtraProperties: true });
-
       const enabled = params.get("pdfbug").split(",");
       try {
         await loadPDFBug();
@@ -335,6 +333,12 @@ const PDFViewerApplication = {
       } catch (ex) {
         console.error("_parseHashParams:", ex);
       }
+
+      const debugOpts = { pdfBug: true, fontExtraProperties: true };
+      if (globalThis.StepperManager?.enabled) {
+        debugOpts.minDurationToUpdateCanvas = 0;
+      }
+      AppOptions.setAll(debugOpts);
     }
     // It is not possible to change locale for the (various) extension builds.
     if (
@@ -519,6 +523,7 @@ const PDFViewerApplication = {
       enableHWA,
       supportsPinchToZoom: this.supportsPinchToZoom,
       enableAutoLinking: AppOptions.get("enableAutoLinking"),
+      minDurationToUpdateCanvas: AppOptions.get("minDurationToUpdateCanvas"),
     }));
 
     renderingQueue.setViewer(pdfViewer);
diff --git a/web/app_options.js b/web/app_options.js
index df4b6a57b4bac..4d4674a7e81f9 100644
--- a/web/app_options.js
+++ b/web/app_options.js
@@ -299,6 +299,11 @@ const defaultOptions = {
     value: 2 ** 25,
     kind: OptionKind.VIEWER,
   },
+  minDurationToUpdateCanvas: {
+    /** @type {number} */
+    value: 500, // ms
+    kind: OptionKind.VIEWER,
+  },
   forcePageColors: {
     /** @type {boolean} */
     value: false,
diff --git a/web/base_pdf_page_view.js b/web/base_pdf_page_view.js
index fa37052a6e670..b7c650586c496 100644
--- a/web/base_pdf_page_view.js
+++ b/web/base_pdf_page_view.js
@@ -21,12 +21,18 @@ class BasePDFPageView {
 
   #loadingId = null;
 
+  #minDurationToUpdateCanvas = 0;
+
   #renderError = null;
 
   #renderingState = RenderingStates.INITIAL;
 
   #showCanvas = null;
 
+  #startTime = 0;
+
+  #tempCanvas = null;
+
   canvas = null;
 
   /** @type {null | HTMLDivElement} */
@@ -51,6 +57,7 @@ class BasePDFPageView {
     this.id = options.id;
     this.pageColors = options.pageColors || null;
     this.renderingQueue = options.renderingQueue;
+    this.#minDurationToUpdateCanvas = options.minDurationToUpdateCanvas ?? 500;
   }
 
   get renderingState() {
@@ -71,6 +78,9 @@ class BasePDFPageView {
     switch (state) {
       case RenderingStates.PAUSED:
         this.div.classList.remove("loading");
+        // Display the canvas as it has been drawn.
+        this.#startTime = 0;
+        this.#showCanvas?.(false);
         break;
       case RenderingStates.RUNNING:
         this.div.classList.add("loadingIcon");
@@ -82,10 +92,12 @@ class BasePDFPageView {
           this.div.classList.add("loading");
           this.#loadingId = null;
         }, 0);
+        this.#startTime = Date.now();
         break;
       case RenderingStates.INITIAL:
       case RenderingStates.FINISHED:
         this.div.classList.remove("loadingIcon", "loading");
+        this.#startTime = 0;
         break;
     }
   }
@@ -100,10 +112,41 @@ class BasePDFPageView {
     // have a final flash we just display it once all the drawing is done.
     const updateOnFirstShow = !prevCanvas && !hasHCM && !hideUntilComplete;
 
-    const canvas = (this.canvas = document.createElement("canvas"));
+    let canvas = (this.canvas = document.createElement("canvas"));
 
     this.#showCanvas = isLastShow => {
       if (updateOnFirstShow) {
+        let tempCanvas = this.#tempCanvas;
+        if (!isLastShow && this.#minDurationToUpdateCanvas > 0) {
+          // We draw on the canvas at 60fps (in using `requestAnimationFrame`),
+          // so if the canvas is large, updating it at 60fps can be a way too
+          // much and can cause some serious performance issues.
+          // To avoid that we only update the canvas every
+          // `this.#minDurationToUpdateCanvas` ms.
+
+          if (Date.now() - this.#startTime < this.#minDurationToUpdateCanvas) {
+            return;
+          }
+          if (!tempCanvas) {
+            tempCanvas = this.#tempCanvas = canvas;
+            canvas = this.canvas = canvas.cloneNode(false);
+            onShow(canvas);
+          }
+        }
+
+        if (tempCanvas) {
+          const ctx = canvas.getContext("2d", {
+            alpha: false,
+          });
+          ctx.drawImage(tempCanvas, 0, 0);
+          if (isLastShow) {
+            this.#resetTempCanvas();
+          } else {
+            this.#startTime = Date.now();
+          }
+          return;
+        }
+
         // Don't add the canvas until the first draw callback, or until
         // drawing is complete when `!this.renderingQueue`, to prevent black
         // flickering.
@@ -152,6 +195,14 @@ class BasePDFPageView {
     canvas.remove();
     canvas.width = canvas.height = 0;
     this.canvas = null;
+    this.#resetTempCanvas();
+  }
+
+  #resetTempCanvas() {
+    if (this.#tempCanvas) {
+      this.#tempCanvas.width = this.#tempCanvas.height = 0;
+      this.#tempCanvas = null;
+    }
   }
 
   async _drawCanvas(options, onCancel, onFinish) {
diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js
index 59c56acf84007..751decf5cbf6a 100644
--- a/web/pdf_viewer.js
+++ b/web/pdf_viewer.js
@@ -139,6 +139,8 @@ function isValidAnnotationEditorMode(mode) {
  *   The default value is `true`.
  * @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from
  *   text that look like URLs. The default value is `true`.
+ * @property {number} [minDurationToUpdateCanvas] - Minimum duration to wait
+ *   before updating the canvas. The default value is `500`.
  */
 
 class PDFPageViewBuffer {
@@ -243,6 +245,8 @@ class PDFViewer {
 
   #eventAbortController = null;
 
+  #minDurationToUpdateCanvas = 0;
+
   #mlManager = null;
 
   #scrollTimeoutId = null;
@@ -342,6 +346,7 @@ class PDFViewer {
     this.#enableHWA = options.enableHWA || false;
     this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;
     this.#enableAutoLinking = options.enableAutoLinking !== false;
+    this.#minDurationToUpdateCanvas = options.minDurationToUpdateCanvas ?? 500;
 
     this.defaultRenderingQueue = !options.renderingQueue;
     if (
@@ -1003,6 +1008,7 @@ class PDFViewer {
             layerProperties: this._layerProperties,
             enableHWA: this.#enableHWA,
             enableAutoLinking: this.#enableAutoLinking,
+            minDurationToUpdateCanvas: this.#minDurationToUpdateCanvas,
           });
           this._pages.push(pageView);
         }


Reviews:
user: login: marco-c
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T09:54:11Z
user: login: Snuffleupagus
body: I think this begs the question if we should just do *all* rendering in the background, since that'd simplify things considerably.

As-is, my primary "issue" with this is that it adds a bunch of (quite frankly undesirable) complexity to the `_createCanvas`-method and it'd be nice not having to do that.

Additionally, it seems that this will effectively make the `Stepper` (in `debugger.mjs`) unusable so this should probably be controlled by a proper `AppOptions`-entry rather than a code-constant (such that it can be disabled when loading the debugger).
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-25T14:35:03Z
user: login: Snuffleupagus
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-25T16:22:58Z
user: login: Snuffleupagus
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-25T18:10:25Z
user: login: Snuffleupagus
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-26T09:43:12Z
user: login: Snuffleupagus
body: This seems to work correctly in my testing, but perhaps this is the sort of feature that could benefit from having QA test zooming (e.g. with big maps) a bit once this patch reaches mozilla-central?

r=me, thank you.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-29T08:01:37Z

Review comments:
diff_hunk: @@ -16,6 +16,12 @@
 import { RenderingCancelledException } from "pdfjs-lib";
 import { RenderingStates } from "./ui_utils.js";
 
+// We draw on the canvas at 60fps (in using `requestAnimationFrame`), so if the
+// canvas is large, updating it at 60fps can be a way too much and can cause
+// some serious performance issues.
+// To avoid that we only update the canvas every 500ms.
+const MIN_DURATION_TO_UPDATE_CANVAS = 500; // ms
path: web/base_pdf_page_view.js
user: login: marco-c
body: We could even increase this (e.g. to 1 sec), so users see that something is being drawn, but we don't spend too much resources to draw too often.
created_at: 2025-04-24T09:54:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -152,10 +193,17 @@ class BasePDFPageView {
     canvas.remove();
     canvas.width = canvas.height = 0;
     this.canvas = null;
+    if (this.#tempCanvas) {
+      this.#tempCanvas.width = this.#tempCanvas.height = 0;
+      this.#tempCanvas = null;
+    }
   }
 
   async _drawCanvas(options, onCancel, onFinish) {
     const renderTask = (this.renderTask = this.pdfPage.render(options));
+    if (this.pdfPage._pdfBug && globalThis.StepperManager?.enabled) {
+      this.#minDurationToUpdateCanvas = 0;
+    }
path: web/base_pdf_page_view.js
user: login: Snuffleupagus
body: Do we need to check this here, can't we set the option together with the others when loading the debugger?
https://github.com/mozilla/pdf.js/blob/efc5c3c2314840098551c325cd7e31d60ef3bd53/web/app.js#L327-L337
created_at: 2025-04-25T16:22:58Z
author_association: COLLABORATOR
diff_hunk: @@ -51,6 +57,7 @@ class BasePDFPageView {
     this.id = options.id;
     this.pageColors = options.pageColors || null;
     this.renderingQueue = options.renderingQueue;
+    this.#minDurationToUpdateCanvas = options.minDurationToUpdateCanvas || 0;
path: web/base_pdf_page_view.js
user: login: Snuffleupagus
body: Should the default value be the same as in `app_options.js`?
created_at: 2025-04-25T18:04:05Z
author_association: COLLABORATOR
diff_hunk: @@ -339,6 +343,7 @@ class PDFViewer {
     this.pageColors = options.pageColors || null;
     this.#mlManager = options.mlManager || null;
     this.#enableHWA = options.enableHWA || false;
+    this.#minDurationToUpdateCanvas = options.minDurationToUpdateCanvas || 0;
path: web/pdf_viewer.js
user: login: Snuffleupagus
body: Should the default value be the same as in `app_options.js`?
created_at: 2025-04-25T18:05:02Z
author_association: COLLABORATOR
diff_hunk: @@ -138,6 +138,8 @@ function isValidAnnotationEditorMode(mode) {
  *   The default value is `true`.
  * @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from
  *   text that look like URLs. The default value is `true`.
+ * @property {number} [minDurationToUpdateCanvas] - Minimum duration to wait
+ *  before updating the canvas.
path: web/pdf_viewer.js
user: login: Snuffleupagus
body: ```suggestion
 *   before updating the canvas.
```
created_at: 2025-04-25T18:06:20Z
author_association: COLLABORATOR
diff_hunk: @@ -138,6 +138,8 @@ function isValidAnnotationEditorMode(mode) {
  *   The default value is `true`.
  * @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from
  *   text that look like URLs. The default value is `true`.
+ * @property {number} [minDurationToUpdateCanvas] - Minimum duration to wait
+ *  before updating the canvas.
path: web/pdf_viewer.js
user: login: Snuffleupagus
body: Please also mentioned the default value in the comment, similar to the other options.
created_at: 2025-04-25T18:08:43Z
author_association: COLLABORATOR
diff_hunk: @@ -152,6 +193,10 @@ class BasePDFPageView {
     canvas.remove();
     canvas.width = canvas.height = 0;
     this.canvas = null;
+    if (this.#tempCanvas) {
+      this.#tempCanvas.width = this.#tempCanvas.height = 0;
+      this.#tempCanvas = null;
+    }
path: web/base_pdf_page_view.js
user: login: Snuffleupagus
body: Should we perhaps add a new helper method, e.g. `#resetTempCanvas`, to avoid repeating this code verbatim?
created_at: 2025-04-25T18:09:48Z
author_association: COLLABORATOR
diff_hunk: @@ -100,10 +112,41 @@ class BasePDFPageView {
     // have a final flash we just display it once all the drawing is done.
     const updateOnFirstShow = !prevCanvas && !hasHCM && !hideUntilComplete;
 
-    const canvas = (this.canvas = document.createElement("canvas"));
+    let canvas = (this.canvas = document.createElement("canvas"));
 
     this.#showCanvas = isLastShow => {
+      let tempCanvas = this.#tempCanvas;
       if (updateOnFirstShow) {
+        if (!isLastShow && this.#minDurationToUpdateCanvas > 0) {
path: web/base_pdf_page_view.js
user: login: Snuffleupagus
body: The new variable probably doesn't need to be unconditionally defined, since it's only used when `updateOnFirstShow === true`?
```suggestion
      if (updateOnFirstShow) {
        let tempCanvas = this.#tempCanvas;
        if (!isLastShow && this.#minDurationToUpdateCanvas > 0) {
```
created_at: 2025-04-26T09:41:10Z
author_association: COLLABORATOR
diff_hunk: @@ -100,10 +112,41 @@ class BasePDFPageView {
     // have a final flash we just display it once all the drawing is done.
     const updateOnFirstShow = !prevCanvas && !hasHCM && !hideUntilComplete;
 
-    const canvas = (this.canvas = document.createElement("canvas"));
+    let canvas = (this.canvas = document.createElement("canvas"));
 
     this.#showCanvas = isLastShow => {
+      let tempCanvas = this.#tempCanvas;
       if (updateOnFirstShow) {
+        if (!isLastShow && this.#minDurationToUpdateCanvas > 0) {
+          // We draw on the canvas at 60fps (in using `requestAnimationFrame`),
+          // so if the canvas is large, updating it at 60fps can be a way too
+          // much and can cause some serious performance issues.
+          // To avoid that we only update the canvas every
+          // minDurationToUpdateCanvas ms.
path: web/base_pdf_page_view.js
user: login: Snuffleupagus
body: Nit: To account for this now longer being a code constant (as in first version of the patch).
```suggestion
          // To avoid that we only update the canvas every
          // `this.#minDurationToUpdateCanvas` ms.
```
created_at: 2025-04-26T09:42:30Z
author_association: COLLABORATOR
diff_hunk: @@ -340,6 +344,7 @@ class PDFViewer {
     this.pageColors = options.pageColors || null;
     this.#mlManager = options.mlManager || null;
     this.#enableHWA = options.enableHWA || false;
+    this.#minDurationToUpdateCanvas = options.minDurationToUpdateCanvas ?? 500;
     this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;
     this.#enableAutoLinking = options.enableAutoLinking !== false;
path: web/pdf_viewer.js
user: login: Snuffleupagus
body: Nit: Please let's keep the ordering between docs and options consistent.
```suggestion
    this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;
    this.#enableAutoLinking = options.enableAutoLinking !== false;
    this.#minDurationToUpdateCanvas = options.minDurationToUpdateCanvas ?? 500;
```
created_at: 2025-04-29T07:54:40Z
author_association: COLLABORATOR
diff_hunk: @@ -1002,6 +1007,7 @@ class PDFViewer {
             l10n: this.l10n,
             layerProperties: this._layerProperties,
             enableHWA: this.#enableHWA,
+            minDurationToUpdateCanvas: this.#minDurationToUpdateCanvas,
             enableAutoLinking: this.#enableAutoLinking,
path: web/pdf_viewer.js
user: login: Snuffleupagus
body: Nit: Please let's keep the ordering between docs and options consistent.
```suggestion
            enableAutoLinking: this.#enableAutoLinking,
            minDurationToUpdateCanvas: this.#minDurationToUpdateCanvas,
```
created_at: 2025-04-29T07:55:03Z
author_association: COLLABORATOR

PR comments:
user: login: calixteman
created_at: 2025-04-23T19:40:17Z
body: /botio test
author_association: CONTRIBUTOR
user: login: moz-tools-bot
created_at: 2025-04-23T19:40:19Z
body: #### From: Bot.io (Windows)

----

# Received

Command **cmd_test** from @calixteman received. Current queue size: 0

Live output at: http://54.193.163.58:8877/45a5c603b34d1d4/output.txt
author_association: COLLABORATOR
user: login: moz-tools-bot
created_at: 2025-04-23T19:40:19Z
body: #### From: Bot.io (Linux m4)

----

# Received

Command **cmd_test** from @calixteman received. Current queue size: 0

Live output at: http://54.241.84.105:8877/d5b6ec797818efa/output.txt
author_association: COLLABORATOR
user: login: moz-tools-bot
created_at: 2025-04-23T20:11:02Z
body: #### From: Bot.io (Linux m4)

----

# Failed

Full output at http://54.241.84.105:8877/d5b6ec797818efa/output.txt

Total script time: 30.69 mins

+ **Unit tests:** Passed
+ **Integration Tests:** FAILED
+ **Regression tests:** Passed
author_association: COLLABORATOR
user: login: moz-tools-bot
created_at: 2025-04-23T20:39:09Z
body: #### From: Bot.io (Windows)

----

# Failed

Full output at http://54.193.163.58:8877/45a5c603b34d1d4/output.txt

Total script time: 58.82 mins

+ **Unit tests:** Passed
+ **Integration Tests:** FAILED
+ **Regression tests:** Passed
author_association: COLLABORATOR
user: login: calixteman
created_at: 2025-04-25T14:26:57Z
body: /botio-linux preview
author_association: CONTRIBUTOR
user: login: moz-tools-bot
created_at: 2025-04-25T14:26:59Z
body: #### From: Bot.io (Linux m4)

----

# Received

Command **cmd_preview** from @calixteman received. Current queue size: 0

Live output at: http://54.241.84.105:8877/b25e54400a20b38/output.txt
author_association: COLLABORATOR
user: login: moz-tools-bot
created_at: 2025-04-25T14:27:52Z
body: #### From: Bot.io (Linux m4)

----

# Success

Full output at http://54.241.84.105:8877/b25e54400a20b38/output.txt

Total script time: 0.87 mins

#### Published

+ Viewer: http://54.241.84.105:8877/b25e54400a20b38/web/viewer.html
+ Viewer (legacy): http://54.241.84.105:8877/b25e54400a20b38/legacy/web/viewer.html
author_association: COLLABORATOR
user: login: calixteman
created_at: 2025-04-25T16:09:17Z
body: I thought about doing all the rendering in background but in case the pdf is too long to render then the user could think that something is broken, so with this version we give some signals that something is happening.

author_association: CONTRIBUTOR
user: login: Snuffleupagus
created_at: 2025-04-25T16:19:21Z
body: > I thought about doing all the rendering in background but in case the pdf is too long to render then the user could think that something is broken, so with this version we give some signals that something is happening.

Doesn't the loading indicators spinners, that exist on both page-element itself and in the page-number input (in the toolbar), already help show that rendering is happening?
author_association: COLLABORATOR
user: login: calixteman
created_at: 2025-04-25T16:36:58Z
body: > > I thought about doing all the rendering in background but in case the pdf is too long to render then the user could think that something is broken, so with this version we give some signals that something is happening.
> 
> Doesn't the loading indicators spinners, that exist on both page-element itself and in the page-number input (in the toolbar), already help show that rendering is happening?

They should help for sure but I'm not totally sure that users in general know that when the spinner is spinning then it means that the rendering is in progress.
For example they could think that there is a network issue/slowdown or something is wrong with Firefox itself.
So at least with a partial rendering we give a clear signal that something is happening: no doubt. 
I want to avoid people trying to reload their tab or start to zoom while waiting (especially on mobile) just see if something happens (like I can do myself when I think something is stuck even if there's a hourglass, a spinner or whatever).
author_association: CONTRIBUTOR
user: login: Snuffleupagus
created_at: 2025-04-25T17:55:49Z
body: What happens if rendering is *paused*, because another page becomes more visible before the delay has been reached?
Should we ensure that we "forcibly" invoke `#showCanvas` in that case, to avoid having a blank canvas in the viewer?
author_association: COLLABORATOR
user: login: Snuffleupagus
created_at: 2025-04-27T11:49:54Z
body: Something else that occurred to me is that we may not want to disable this unconditionally when the debugger is used, such that e.g. http://localhost:8888/web/viewer.html#pdfBug=Stats can be used to still get "correct" performance numbers.

```diff
diff --git a/web/app.js b/web/app.js
index 1e245ec38..d94c99fb2 100644
--- a/web/app.js
+++ b/web/app.js
@@ -326,8 +326,6 @@ const PDFViewerApplication = {
       }
     }
     if (params.has("pdfbug")) {
-      AppOptions.setAll({ pdfBug: true, fontExtraProperties: true });
-
       const enabled = params.get("pdfbug").split(",");
       try {
         await loadPDFBug();
@@ -335,6 +333,12 @@ const PDFViewerApplication = {
       } catch (ex) {
         console.error("_parseHashParams:", ex);
       }
+
+      const debugOpts = { pdfBug: true, fontExtraProperties: true };
+      if (globalThis.StepperManager?.enabled) {
+        debugOpts.minDurationToUpdateCanvas = 0;
+      }
+      AppOptions.setAll(debugOpts);
     }
     // It is not possible to change locale for the (various) extension builds.
     if (
```
author_association: COLLABORATOR
user: login: Snuffleupagus
created_at: 2025-04-27T14:11:28Z
body: Another question, should we lower the time before the page loading indicator is displayed now?
https://github.com/mozilla/pdf.js/blob/b47b248e15e9f4fdae7d4847218ccb3195c29ce6/web/pdf_viewer.css#L31

Currently it's obvious that rendering is happening, since the page renders incrementally and visibly, however with the changes in this patch there will be less *initial* indication of rendering being in progress.
author_association: COLLABORATOR
user: login: calixteman
created_at: 2025-04-28T11:36:09Z
body: > Another question, should we lower the time before the page loading indicator is displayed now?
> 
> https://github.com/mozilla/pdf.js/blob/b47b248e15e9f4fdae7d4847218ccb3195c29ce6/web/pdf_viewer.css#L31
> 
> Currently it's obvious that rendering is happening, since the page renders incrementally and visibly, however with the changes in this patch there will be less _initial_ indication of rendering being in progress.

Honestly, I'm not sure that a "normal" human would make a difference between having 100 or 200 ms here vs 400ms.
I mean the time used to formalize the thought "what's happening with pdf ???" and the display of the spinner is probably something around that.
At least in not reducing the value we don't loose few cycles (and mW).
It's the kind of thing we could change in a follow-up if some users complain.
author_association: CONTRIBUTOR
user: login: Snuffleupagus
created_at: 2025-04-29T07:13:44Z
body: /botio-linux preview
author_association: COLLABORATOR
user: login: moz-tools-bot
created_at: 2025-04-29T07:13:47Z
body: #### From: Bot.io (Linux m4)

----

# Received

Command **cmd_preview** from @Snuffleupagus received. Current queue size: 0

Live output at: http://54.241.84.105:8877/dce77563326740a/output.txt
author_association: COLLABORATOR
user: login: moz-tools-bot
created_at: 2025-04-29T07:14:53Z
body: #### From: Bot.io (Linux m4)

----

# Success

Full output at http://54.241.84.105:8877/dce77563326740a/output.txt

Total script time: 1.09 mins

#### Published

+ Viewer: http://54.241.84.105:8877/dce77563326740a/web/viewer.html
+ Viewer (legacy): http://54.241.84.105:8877/dce77563326740a/legacy/web/viewer.html
author_association: COLLABORATOR
user: login: calixteman
created_at: 2025-04-29T16:35:41Z
body: /botio test
author_association: CONTRIBUTOR
user: login: moz-tools-bot
created_at: 2025-04-29T16:35:43Z
body: #### From: Bot.io (Linux m4)

----

# Received

Command **cmd_test** from @calixteman received. Current queue size: 1

Live output at: http://54.241.84.105:8877/212106326d2a07f/output.txt
author_association: COLLABORATOR
user: login: moz-tools-bot
created_at: 2025-04-29T16:35:44Z
body: #### From: Bot.io (Windows)

----

# Received

Command **cmd_test** from @calixteman received. Current queue size: 1

Live output at: http://54.193.163.58:8877/4e60bd3e706dc0f/output.txt
author_association: COLLABORATOR
user: login: moz-tools-bot
created_at: 2025-04-29T17:28:44Z
body: #### From: Bot.io (Linux m4)

----

# Failed

Full output at http://54.241.84.105:8877/212106326d2a07f/output.txt

Total script time: 30.42 mins

+ **Unit tests:** Passed
+ **Integration Tests:** FAILED
+ **Regression tests:** Passed
author_association: COLLABORATOR
user: login: moz-tools-bot
created_at: 2025-04-29T18:30:41Z
body: #### From: Bot.io (Windows)

----

# Failed

Full output at http://54.193.163.58:8877/4e60bd3e706dc0f/output.txt

Total script time: 61.23 mins

+ **Unit tests:** FAILED
+ **Integration Tests:** FAILED
+ **Regression tests:** Passed
author_association: COLLABORATOR