We are using UltimatePDF forge components action PrintToPDF_Advanced and we notice the poor performance of this forge
This is our set up of ActionGenerateNonCompliancePDF
We have these logs sequence:
From this, the first action in PDF Screen performed at start at 15:16:59, meaning ODC server took 9 secs to reach the URL.
Afterward, our data processing is performed and took 4 secs from 15:16:59 to 15:17:03.
At last the convert from html to pdf of PrintToPDF_Advanced took 2 secs from 15:17:03 to 15:17:05
So our questions is:
Thank you!
Hi Nguyen,
Great debugging work with the timestamps - that breakdown is very helpful in narrowing down what's going on.
The 9-second gap between PrintToPDF_Advanced starting and the first action executing on your PDF Screen points to startup overhead in UltimatePDF's rendering pipeline, which in ODC involves more than meets the eye.
What's actually happening under the hood:
In ODC, PrintToPDF_Advanced is implemented as https://github.com/OutSystems/UltimatePDF-ExternalLogic - C# code running in an isolated container, separate from your app.
Here's the actual flow:
1. Your app calls PrintToPDF_Advanced -> HTTPS call to the External Logic container
2. The External Logic code launches a headless Chromium browser. Although the source code includes a browser instance pool, the browser is explicitly closed after each PDF generation (Browser.CloseAsync()). On the next call, the pool finds no healthy instance and launches a fresh one - so a Chromium startup cost is incurred on every call.
3. Chromium navigates to your PDF Screen URL (GoToAsync waiting for DOMContentLoaded, Load, and Networkidle0). This single call doesn't return until the page fully loads and all network activity has been quiet for at least 500ms. The 9-second gap you observed is the time before your screen even starts responding - Chromium has made the request, but your app hasn't begun executing yet. Once your screen responds, your 4 seconds of data processing also happen within this same navigation call.
4. Chromium waits for a CSS readiness signal (the page removing a ultimate-pdf-is-not-ready class) before proceeding.
5. Chromium converts the rendered HTML to PDF (your 2 seconds).
So the 9-second gap (before your screen-level actions begin) is a combination of:
- Chromium launch (present on every call): A fresh browser process starts each time, as described above. In my experience, the launch time varies depending on container state - faster when the External Logic container has been recently active (binaries already in memory), slower after periods of inactivity.
- External Logic container startup (variable): If the container hosting UltimatePDF has been idle, there may be additional startup latency as the platform spins it up, similar to how ODC app containers behave after periods of inactivity.
- App container cold start (variable): If your own app has also been idle, the HTTP request from Chromium back to your PDF Screen may face additional startup latency.
What you can do:
1. Measure the cold start factor: Run the PDF generation twice in quick succession. Both runs include Chromium launch, but the second run avoids container cold starts, giving you a baseline. The difference between the two reveals how much of the 9 seconds comes from containers being idle.
2. Keep your app container warm: Install and deploy the WakeUp Forge component and subscribe to its Ping_15 event (fires every 15 minutes). This keeps your app container active, reducing cold start latency when Chromium calls back to your PDF Screen. This addresses only the app container portion - it won't affect Chromium launch time or the External Logic container.
3. Keep the PDF Screen lean: Since the navigation waits for Networkidle0 (500ms of zero network connections), every external resource your PDF Screen loads - web fonts, external CSS, async data fetches - extends the wait. Keep the screen as self-contained as possible: inline critical styles, avoid external font loading, minimize the number of network requests, and simplify data fetching to reduce both the Networkidle0 wait and overall processing time.
4. Generate asynchronously: If end users don't need the PDF immediately, trigger the generation via a Timer and notify when ready. This doesn't reduce the total generation time, but removes the wait from the user experience.
To directly answer your three questions:
- Why 9 seconds? Chromium browser launch (on every call), potential container cold starts (both the External Logic container and your app), and the initial HTTP request from Chromium to your PDF Screen URL.
- What's happening during those 9 seconds? The platform routes the call to the External Logic container, a fresh Chromium instance is launched, and Chromium makes an HTTP request to your PDF Screen URL - at which point your screen-level actions begin executing.
- How to optimize? Measure the cold start factor (test twice), keep your app warm (WakeUp), keep the PDF Screen lean, and consider async generation for better UX.
Hope this helps!