Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b19bec7ee5 | ||
|
|
987ef29f30 |
@@ -5,8 +5,6 @@ create_2_Open_Interface_Spec.txt
|
|||||||
|
|
||||||
logs
|
logs
|
||||||
node_modules/
|
node_modules/
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
.pio
|
.pio
|
||||||
.vscode/
|
.vscode/
|
||||||
config.h
|
config.h
|
||||||
@@ -16,25 +14,5 @@ server/config.yaml
|
|||||||
server/package-lock.json
|
server/package-lock.json
|
||||||
package-lock.json
|
package-lock.json
|
||||||
server/data/discord-guilds.json
|
server/data/discord-guilds.json
|
||||||
server/data/global-objective.json
|
server/data/community-goal.json
|
||||||
server/data/admin-reason.json
|
server/data
|
||||||
server/data/buttonbox-state.json
|
|
||||||
server/data/barcode-tts-cache/
|
|
||||||
server/data/rover-odometers.json
|
|
||||||
server/data/mediamtx.yml
|
|
||||||
webui/package-lock.json
|
|
||||||
!server/data/
|
|
||||||
!server/data/barcode-registry.json
|
|
||||||
webui/src/config/analytics.jsx
|
|
||||||
webui/src/config/driverAnalytics.json
|
|
||||||
server/data/analytics.html
|
|
||||||
plans/barcodegames.txt
|
|
||||||
.gitignore
|
|
||||||
server/data/identity.sqlite
|
|
||||||
server/data/barcode-games.json
|
|
||||||
server/data/identity.sqlite-shm
|
|
||||||
server/data/identity.sqlite-wal
|
|
||||||
server/src/services/balanceBoardService/native/balance_board_worker
|
|
||||||
server/data/fleet-reports.sqlite
|
|
||||||
server/data/fleet-reports.sqlite-shm
|
|
||||||
server/data/fleet-reports.sqlite-wal
|
|
||||||
|
|||||||
@@ -1,74 +1,89 @@
|
|||||||
# Multi Roomba Rover
|
# Multi Roomba Rover
|
||||||
A system for controlling create 2 compatible roombas through a webpage.
|
|
||||||
|
|
||||||
You can explore my basement through this project here:
|
a remake of my RoombaRover project with a decentralized and embedded approach
|
||||||
https://rover.otter.land
|
supports multiple roombas
|
||||||
|
|
||||||
*some of this code was created with help from large language models, and some of it was written by me. This project would not have been possible for me to create without it.*
|
on each roomba:
|
||||||
|
- a raspberry pi zero 2 w
|
||||||
|
- a raspberry pi camera
|
||||||
|
- roomba's serial port hooked up to the built in serial port on the raspberry pi
|
||||||
|
|
||||||
## This guide is a work in progress, it will cover:
|
pi provisioning:
|
||||||
- Building rovers
|
- enable serial port
|
||||||
- Installing roverd on a rover's raspberry pi
|
- disable wifi powersave
|
||||||
- Setting up the server
|
- disable bluetooth
|
||||||
- Configuration
|
|
||||||
|
|
||||||
## Building a rover
|
## Repo layout
|
||||||
You WILL need some common hobby electronics knowledge to build one of these, I will not be making a super detailed wiring or assembly guide, some of building your rover will be up to you, things such as a protective cage, camera and electronics mounting will not be covered in any great detail.
|
|
||||||
|
|
||||||
### Parts per rover
|
- `pi/roverd`: tiny Go daemon that bridges the Create 2 serial port, BRC pin, and the control server via WebSockets.
|
||||||
|
- `server`: Node.js process that terminates rover sockets, relays commands to/from the Socket.IO UI, and serves `public/`.
|
||||||
|
- `pi/bin` / `pi/systemd`: helper scripts + systemd units for the rover (roverd itself plus the SRT video publisher that streams camera feeds to the server).
|
||||||
|
- `docs/pi-deployment.md`: per-rover build + install instructions (cross-compiling on Fedora 43, deploying roverd + mediaMTX).
|
||||||
|
|
||||||
| Part | Notes |
|
## Quick start
|
||||||
| --- | --- |
|
|
||||||
| Create 2 compatible Roomba with the mini DIN port | Tested only with 600 series Roombas, your mileage may vary with newer models |
|
|
||||||
| 4S li-ion -> 5v Voltage regulator | I use cheapo 12-24v USB car chargers, tear them apart, and solder wires onto them |
|
|
||||||
| 7 pin mini DIN cable | Adafruit sells these labeled as a cable specifically for the iRobot Create 2 |
|
|
||||||
| Logic level shifter | I use ones from adafruit, the Roomba is 5v logic and the pi is 3.3v logic |
|
|
||||||
| Google voice AIY hat v1 | This serves three purposes: speaker, microphone, and a high current LED driver. None of these things are required to make a rover with a camera that can drive around, but it makes it much more fun. The design of this hat also makes it very easy to connect all of the various wires to all the IO on the pi that they need to go to. |
|
|
||||||
| Raspberry pi zero 2 W | This will run roverd to control the rover and manage some Roomba safety things locally. You can probably use any pi you want, but these are usually the cheapest. |
|
|
||||||
| A good wifi adapter | Has to work with Linux 6.12 (I use ones with the MT7921AU chipset and they work great), ideally wifi 6 with MU-MIMO if you are running multiple rovers on one AP. The built in wifi on the pi zero 2 W is NOT good enough for this. |
|
|
||||||
| A USB hub hat (if using a pi zero) | If you are using a raspberry pi zero, you will want a USB hub hat for your wifi adapter, I would not recommend using an OTG adapter as it may not provide enough power. |
|
|
||||||
| ----CAMERA STUFF---- | ----Below here is all camera related things that youll need---- |
|
|
||||||
| A pi camera v2 (ov5647) | The video publisher in this project is only designed for the ov5647, though it would be totally possible to make your own publisher for other sensors. |
|
|
||||||
| Cheap 5v servo | Unless you have a VERY wide camera lens, you will want to have a servo plugged into the pi to be able to tilt the camera up and down. |
|
|
||||||
| LED for toggling the headlight | I use white LEDs attached to one of the "driver" pins on the AIY hat to trick one of those generic pi camera LED floodlights into turning on / off on demand.|
|
|
||||||
| Optional laser pointer | A laser pointer can be wired to another high-current-capable GPIO driver, with GPIO 27 used by default in the rover config. |
|
|
||||||
| Pi zero camera cable | The pi zero has a smaller version of the camera ribbon connector, you will need the right cable for your camera |
|
|
||||||
|
|
||||||
I do not have any exact numbers, but with cheap used Roombas it seems like on average one of my rovers takes about $100 USD to build from scratch.
|
```bash
|
||||||
|
# build the Pi agent (armv7)
|
||||||
|
cd pi/roverd
|
||||||
|
mkdir -p ../../dist
|
||||||
|
make pi-build
|
||||||
|
|
||||||
### Physical assembly of the rover
|
# start the server + UI
|
||||||
This is going to be the very loose section of this guide, the way yout build your rover is up to you. I have mine built out with durable protective metal cages as they are open to the internet and people like breaking things. The electronics on top of the roomba can be as simple as a pi, level shifter, and a camera if you just want to run it around yourself, things like a speaker, microphone, servo, headlight, laser pointer, are all totally optional and can be disabled in the configuration on the raspberry pi.
|
cd ../../server
|
||||||
|
npm install
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
These are the basics of how evertything is connected on my rovers, this is hard to organize and illustrate through text but hopefully it will give you a good idea:
|
Need a fake rover or multi-rover testing without hardware? Use the dummy build:
|
||||||
1. 7 pin mini DIN cable
|
|
||||||
1. Voltage regulator on the two pairs of VBAT wires
|
|
||||||
1. 5v output of voltage regulator connected to the power input on the AIY hat
|
|
||||||
2. level shifter's high voltage side attached to 5v from regulator, ground, TX, RX, and BRC from the DIN cable
|
|
||||||
2. Level shifter low voltage side connected to 3.3v from the pi, with serial TX and RX, and the BRC pin on their respective low side level shifter lines. BRC pin goes to GPIO 4 on the pi.
|
|
||||||
3. Google voice AIY hat:
|
|
||||||
1. speaker
|
|
||||||
2. microphone
|
|
||||||
3. servo motor on GPIO pin 26
|
|
||||||
4. Camera stuff:
|
|
||||||
1. ov5647 camera
|
|
||||||
2. full size to pi zero style camera ribbon cable, plug camera into pi
|
|
||||||
3. an LED connected to a high current driver output on the AIY hat, GPIO 22 by default. This is meant to be poked into the LDR sensor on a pi camera IR floodlight to make it toggleable by the driver.
|
|
||||||
4. optionally, a laser pointer connected to another high current driver output, GPIO 27 by default.
|
|
||||||
|
|
||||||
### Software on your rover
|
```bash
|
||||||
Raspberry pi OS installation:
|
cd pi/roverd
|
||||||
|
mkdir -p ../../dist
|
||||||
|
make dummy
|
||||||
|
./../../dist/roverd-dummy -config ./roverd.sample.yaml
|
||||||
|
```
|
||||||
|
|
||||||
Everything that runs on the pi is designed for debian Bookworm, because I could not get the AIY voice hat working in Trixie.
|
The dummy binary connects to the Node server, emits simulated sensor frames, and logs every command it receives, so you can spin up as many virtual rovers as you’d like on your dev machine.
|
||||||
|
|
||||||
To install roverd on your pi:
|
## Admin config & authentication
|
||||||
1. Install `Raspberry Pi OS (Legacy) Lite` (exactly what its called in the imager) on your SD card
|
|
||||||
2. Boot the pi up, plug in your good wifi adapter, use `nmtui` to set it up
|
|
||||||
3. Install `git`, clone this repo into your home folder
|
|
||||||
4. Move into the repo folder, run the roverd installer using `sudo pi/install_roverd.sh`
|
|
||||||
5. Reboot your raspberry pi
|
|
||||||
6. Roverd, along with the audio and video publishers should now be running. Roverd should have created it's default config file at `/etc/roverd.yaml`
|
|
||||||
|
|
||||||
Edit the `/etc/roverd.yaml` config file to change settings like your rover's name, color, and set the server IP.
|
Before running the Node server, copy `server/config.example.yaml` to `server/config.yaml` and customize the admin records (password hashes, Discord IDs, lockdown permission). Those credentials are used by the driver UI’s login panel—only admins can toggle locks/modes, and lockdown admins retain access when the system enters lockdown mode. The spectator page (future) can set `role:set` to `spectator`, and the server enforces all permissions server-side so client tweaks can’t grant extra control.
|
||||||
|
|
||||||
|
Deploy a rover by copying the repo + `dist/roverd` to the Pi and running the helper (it installs roverd plus the SRT video publisher service):
|
||||||
|
|
||||||
## Setting up the server
|
```bash
|
||||||
|
cd ~/MultiRoombaRover
|
||||||
|
sudo ./pi/install_roverd.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Then point each rover's `/etc/roverd.yaml` at `ws://<server>:8080/rover`, set `name` to the rover’s ID, and (optionally) override `media.publishUrl` if your control server isn’t `192.168.0.86`. The video publisher service (`video-publisher.service`) captures the Pi camera with `rpicam-vid`/`libcamera-vid`, pipes the raw H264 into the stock FFmpeg binary, and publishes via SRT to the server’s mediaMTX instance at `srt://<server>:9000?streamid=#!::r=<name>,m=publish`. The installer now just pulls `libcamera-apps` + `ffmpeg` from apt (no custom build). Use the “Restart Camera” button if you enable media management so roverd can bounce the publisher service remotely.
|
||||||
|
Heads-up: the BRC pulser now uses libgpiod; make sure the `roverd` service account is in the `gpio` group (or otherwise allowed to access `/dev/gpiochip*`) and set `brc.gpioChip` if your hardware exposes a different chip name.
|
||||||
|
|
||||||
|
## Fedora server deployment
|
||||||
|
|
||||||
|
Run the installer from inside the `server/` directory after cloning the repo onto your Fedora 43 Server box:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/MultiRoombaRover/server
|
||||||
|
sudo ./install_server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The script must be executed via `sudo` from the user that owns the repo. It will:
|
||||||
|
|
||||||
|
- install Node.js/npm plus curl/tar
|
||||||
|
- run `npm install --production`
|
||||||
|
- copy `config.example.yaml` to `config.yaml` if needed (edit the file afterwards for admins + `media.whepBaseUrl`)
|
||||||
|
- download mediaMTX v1.15.3 and drop it into `/usr/local/bin`
|
||||||
|
- write `/etc/mediamtx/mediamtx.yml` from `server/mediamtx/mediamtx.yml` (SRT ingest on :9000, open ingest, viewer auth webhook at `/mediamtx/auth`)
|
||||||
|
- create + enable `mediamtx.service` and `multirover.service`, both running as your repo user and pointing at the clone directly
|
||||||
|
|
||||||
|
Publishing rovers lives on a trusted network, so the shipped config (tracked at `server/mediamtx/mediamtx.yml`) skips HTTP auth for SRT ingest and whitelists any path that matches `rover-*`. The installer overwrites `/etc/mediamtx/mediamtx.yml` every time you run it—if you need to tweak ports or add TURN servers, edit the template in the repo and rerun `install_server.sh` so every box stays in sync automatically.
|
||||||
|
|
||||||
|
Once finished, update `server/config.yaml` with your admin passwords and `media.whepBaseUrl` (set it to the URL you expose publicly, e.g. `https://rover.otter.land/video`). If your proxy can’t rewrite paths, create the `/video` location there and add a custom nginx snippet to rewrite `/video/<rover>/whep` to `/<>/whep` before forwarding to mediaMTX. Restart `multirover.service` whenever you edit the config. To pull updates later, just `git pull`, re-run `npm install --production` inside `server/`, and restart the service—no need to rerun the installer.
|
||||||
|
|
||||||
|
Room cameras now use JPEG snapshots (4 fps) instead of WHEP. Each entry in `roomCameras` must include a `url` pointing at the snapshot endpoint; the server polls and relays frames over socket.io with the same access rules as before.
|
||||||
|
|
||||||
|
### Video handshake + diagnostics
|
||||||
|
|
||||||
|
- Every `video:request` returns `{ url, token }`. The browser posts the SDP offer to `url` and includes `Authorization: Basic base64(token:token)`. mediaMTX forwards the username (`token`) to `/mediamtx/auth`, which checks the socket’s permissions (driver assignment, admin/spectator role, lockdown state) and either returns 200 or 401—no query parameters are involved anymore.
|
||||||
|
- To see what mediaMTX is ingesting from the Pis, run `npm run check:media` (or `node scripts/checkMedia.js`). It hits `/v3/paths/list` and prints each rover’s `ready` state and byte counters so you can instantly spot publish issues.
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
.pio
|
|
||||||
.vscode/.browse.c_cpp.db*
|
|
||||||
.vscode/c_cpp_properties.json
|
|
||||||
.vscode/launch.json
|
|
||||||
.vscode/ipch
|
|
||||||
config.h
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
This directory is intended for project header files.
|
|
||||||
|
|
||||||
A header file is a file containing C declarations and macro definitions
|
|
||||||
to be shared between several project source files. You request the use of a
|
|
||||||
header file in your project source file (C, C++, etc) located in `src` folder
|
|
||||||
by including it, with the C preprocessing directive `#include'.
|
|
||||||
|
|
||||||
```src/main.c
|
|
||||||
|
|
||||||
#include "header.h"
|
|
||||||
|
|
||||||
int main (void)
|
|
||||||
{
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Including a header file produces the same results as copying the header file
|
|
||||||
into each source file that needs it. Such copying would be time-consuming
|
|
||||||
and error-prone. With a header file, the related declarations appear
|
|
||||||
in only one place. If they need to be changed, they can be changed in one
|
|
||||||
place, and programs that include the header file will automatically use the
|
|
||||||
new version when next recompiled. The header file eliminates the labor of
|
|
||||||
finding and changing all the copies as well as the risk that a failure to
|
|
||||||
find one copy will result in inconsistencies within a program.
|
|
||||||
|
|
||||||
In C, the convention is to give header files names that end with `.h'.
|
|
||||||
|
|
||||||
Read more about using header files in official GCC documentation:
|
|
||||||
|
|
||||||
* Include Syntax
|
|
||||||
* Include Operation
|
|
||||||
* Once-Only Headers
|
|
||||||
* Computed Includes
|
|
||||||
|
|
||||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
|
|
||||||
This directory is intended for project specific (private) libraries.
|
|
||||||
PlatformIO will compile them to static libraries and link into the executable file.
|
|
||||||
|
|
||||||
The source code of each library should be placed in a separate directory
|
|
||||||
("lib/your_library_name/[Code]").
|
|
||||||
|
|
||||||
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
|
||||||
|
|
||||||
|--lib
|
|
||||||
| |
|
|
||||||
| |--Bar
|
|
||||||
| | |--docs
|
|
||||||
| | |--examples
|
|
||||||
| | |--src
|
|
||||||
| | |- Bar.c
|
|
||||||
| | |- Bar.h
|
|
||||||
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
|
||||||
| |
|
|
||||||
| |--Foo
|
|
||||||
| | |- Foo.c
|
|
||||||
| | |- Foo.h
|
|
||||||
| |
|
|
||||||
| |- README --> THIS FILE
|
|
||||||
|
|
|
||||||
|- platformio.ini
|
|
||||||
|--src
|
|
||||||
|- main.c
|
|
||||||
|
|
||||||
Example contents of `src/main.c` using Foo and Bar:
|
|
||||||
```
|
|
||||||
#include <Foo.h>
|
|
||||||
#include <Bar.h>
|
|
||||||
|
|
||||||
int main (void)
|
|
||||||
{
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
The PlatformIO Library Dependency Finder will find automatically dependent
|
|
||||||
libraries by scanning project source files.
|
|
||||||
|
|
||||||
More information about PlatformIO Library Dependency Finder
|
|
||||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
; PlatformIO Project Configuration File
|
|
||||||
;
|
|
||||||
; Build options: build flags, source filter
|
|
||||||
; Upload options: custom upload port, speed and extra flags
|
|
||||||
; Library options: dependencies, extra library storages
|
|
||||||
; Advanced options: extra scripting
|
|
||||||
;
|
|
||||||
; Please visit documentation for the other options and examples
|
|
||||||
; https://docs.platformio.org/page/projectconf.html
|
|
||||||
|
|
||||||
[env:seeed_xiao_esp32s3]
|
|
||||||
platform = espressif32
|
|
||||||
board = esp32-s3-devkitc-1
|
|
||||||
framework = arduino
|
|
||||||
monitor_speed = 115200
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#define WIFI_SSID "wifissid"
|
|
||||||
#define WIFI_PASSWORD "wifipassword"
|
|
||||||
#define SERVER_URL "http://192.168.0.86:8080/buttonbox/press"
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
#include <Arduino.h>
|
|
||||||
#include <WiFi.h>
|
|
||||||
#include <HTTPClient.h>
|
|
||||||
|
|
||||||
#include <config.h>
|
|
||||||
|
|
||||||
// button pin defs
|
|
||||||
const int buttonPins[] = {15, 16, 17, 18};
|
|
||||||
const int buttonCount = sizeof(buttonPins) / sizeof(buttonPins[0]);
|
|
||||||
const int beeperPin = 13;
|
|
||||||
|
|
||||||
// Using INPUT_PULLUP means idle = HIGH, pressed = LOW (button to GND).
|
|
||||||
const int buttonPressedState = LOW;
|
|
||||||
const unsigned long debounceMs = 40;
|
|
||||||
const int toneDurationMs = 90;
|
|
||||||
|
|
||||||
// One tone per button (1-4).
|
|
||||||
const int buttonTonesHz[buttonCount] = {262, 330, 392, 523};
|
|
||||||
|
|
||||||
int lastStableState[buttonCount];
|
|
||||||
int lastReading[buttonCount];
|
|
||||||
unsigned long lastDebounceTime[buttonCount];
|
|
||||||
|
|
||||||
void PlayButtonTone(int buttonNumber) {
|
|
||||||
if (buttonNumber < 1 || buttonNumber > buttonCount) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int frequencyHz = buttonTonesHz[buttonNumber - 1];
|
|
||||||
tone(beeperPin, frequencyHz, toneDurationMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// button request function
|
|
||||||
bool SendButtonPressRequest(int buttonNumber) {
|
|
||||||
Serial.println("attempting to send button press");
|
|
||||||
bool success = false;
|
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
|
||||||
HTTPClient http;
|
|
||||||
http.begin(SERVER_URL);
|
|
||||||
http.addHeader("Content-Type", "text/plain");
|
|
||||||
|
|
||||||
int httpResponseCode = http.POST(String(buttonNumber));
|
|
||||||
|
|
||||||
if (httpResponseCode > 0) {
|
|
||||||
Serial.println("http response code: ");
|
|
||||||
Serial.print(httpResponseCode);
|
|
||||||
Serial.println(http.getString());
|
|
||||||
success = true;
|
|
||||||
} else {
|
|
||||||
Serial.println("HTTP ERROR!!! ");
|
|
||||||
Serial.print(httpResponseCode);
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
http.end();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Serial.println("Not ocnnectec to wifi! cant send request!");
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
|
||||||
while (WiFi.status() != WL_CONNECTED) {
|
|
||||||
delay(100);
|
|
||||||
Serial.print("wifi connecting... ");
|
|
||||||
}
|
|
||||||
Serial.println("WIFI CONNENCTED !!!! :3");
|
|
||||||
|
|
||||||
for (int i = 0; i < buttonCount; i++) {
|
|
||||||
pinMode(buttonPins[i], INPUT_PULLUP);
|
|
||||||
int initial = digitalRead(buttonPins[i]);
|
|
||||||
lastStableState[i] = initial;
|
|
||||||
lastReading[i] = initial;
|
|
||||||
lastDebounceTime[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pinMode(beeperPin, OUTPUT);
|
|
||||||
noTone(beeperPin);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
for (int i = 0; i < buttonCount; i++) {
|
|
||||||
int reading = digitalRead(buttonPins[i]);
|
|
||||||
|
|
||||||
if (reading != lastReading[i]) {
|
|
||||||
lastDebounceTime[i] = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((now - lastDebounceTime[i]) > debounceMs) {
|
|
||||||
if (reading != lastStableState[i]) {
|
|
||||||
lastStableState[i] = reading;
|
|
||||||
|
|
||||||
// Trigger once on press edge.
|
|
||||||
if (reading == buttonPressedState) {
|
|
||||||
if (SendButtonPressRequest(i + 1)) {
|
|
||||||
PlayButtonTone(i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastReading[i] = reading;
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(5);
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
This directory is intended for PlatformIO Test Runner and project tests.
|
|
||||||
|
|
||||||
Unit Testing is a software testing method by which individual units of
|
|
||||||
source code, sets of one or more MCU program modules together with associated
|
|
||||||
control data, usage procedures, and operating procedures, are tested to
|
|
||||||
determine whether they are fit for use. Unit testing finds problems early
|
|
||||||
in the development cycle.
|
|
||||||
|
|
||||||
More information about PlatformIO Unit Testing:
|
|
||||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
|
||||||
@@ -18,7 +18,5 @@ media:
|
|||||||
service: mediamtx.service
|
service: mediamtx.service
|
||||||
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
||||||
healthInterval: 30s
|
healthInterval: 30s
|
||||||
headlight:
|
nightVision:
|
||||||
enabled: false
|
|
||||||
laser:
|
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|||||||
@@ -18,7 +18,5 @@ media:
|
|||||||
service: mediamtx.service
|
service: mediamtx.service
|
||||||
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
||||||
healthInterval: 30s
|
healthInterval: 30s
|
||||||
headlight:
|
nightVision:
|
||||||
enabled: false
|
|
||||||
laser:
|
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|||||||
@@ -18,7 +18,5 @@ media:
|
|||||||
service: mediamtx.service
|
service: mediamtx.service
|
||||||
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
||||||
healthInterval: 30s
|
healthInterval: 30s
|
||||||
headlight:
|
nightVision:
|
||||||
enabled: false
|
|
||||||
laser:
|
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
New Drive corner-pod HUD sketch, second design pass
|
|
||||||
Purpose: Shows true edge-mounted pods, physically attached expansions, and circular controls.
|
|
||||||
Scope: Static design communication only; production geometry remains an implementation decision.
|
|
||||||
-->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="900" viewBox="0 0 1200 900" role="img" aria-labelledby="title description">
|
|
||||||
<title id="title">Edge-mounted New Drive HUD pods</title>
|
|
||||||
<desc id="description">Four pods flow directly into the corners of a four by three rover video. Each has one inward rounded corner, and expansions attach directly along video edges.</desc>
|
|
||||||
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="video" x1="0" y1="0" x2="1" y2="1">
|
|
||||||
<stop offset="0" stop-color="#26343d" />
|
|
||||||
<stop offset="0.55" stop-color="#111827" />
|
|
||||||
<stop offset="1" stop-color="#1f2937" />
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="battery" x1="0" y1="1" x2="1" y2="0">
|
|
||||||
<stop offset="0" stop-color="#22c55e" />
|
|
||||||
<stop offset="0.72" stop-color="#84cc16" />
|
|
||||||
<stop offset="1" stop-color="#eab308" />
|
|
||||||
</linearGradient>
|
|
||||||
<filter id="shadow" x="-30%" y="-30%" width="160%" height="160%">
|
|
||||||
<feDropShadow dx="0" dy="5" stdDeviation="8" flood-color="#000000" flood-opacity="0.5" />
|
|
||||||
</filter>
|
|
||||||
<style>
|
|
||||||
.pod { fill: #080a0f; fill-opacity: 0.86; stroke: #d1d5db; stroke-opacity: 0.26; stroke-width: 2; }
|
|
||||||
.expansion { fill: #080a0f; fill-opacity: 0.82; stroke: #d1d5db; stroke-opacity: 0.2; stroke-width: 2; }
|
|
||||||
.label { fill: #f8fafc; font-family: Inter, system-ui, sans-serif; font-weight: 700; }
|
|
||||||
.small { fill: #cbd5e1; font-family: Inter, system-ui, sans-serif; font-size: 17px; }
|
|
||||||
.tiny { fill: #94a3b8; font-family: Inter, system-ui, sans-serif; font-size: 14px; }
|
|
||||||
.arrow-button { fill: #1f2937; stroke: #e5e7eb; stroke-opacity: 0.55; stroke-width: 1.5; }
|
|
||||||
.arrow { fill: none; stroke: #f8fafc; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
|
|
||||||
.track { fill: none; stroke: #334155; stroke-linecap: round; }
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<!-- The entire canvas is the shared 4:3 video/HUD coordinate space. -->
|
|
||||||
<rect width="1200" height="900" fill="url(#video)" />
|
|
||||||
<path d="M0 610 C235 510 390 590 600 515 C820 438 1000 520 1200 430 L1200 900 L0 900 Z" fill="#0b1516" opacity="0.76" />
|
|
||||||
<path d="M0 655 C240 555 425 635 630 560 C840 483 1020 560 1200 475" fill="none" stroke="#334155" stroke-width="5" opacity="0.42" />
|
|
||||||
<text x="600" y="450" text-anchor="middle" class="small" opacity="0.28">Rover video</text>
|
|
||||||
|
|
||||||
<!-- Top-left pod flows into the top and left edges; only its inward bottom-right corner rounds. -->
|
|
||||||
<g filter="url(#shadow)">
|
|
||||||
<path class="pod" d="M0 0 H190 V124 Q190 190 124 190 H0 Z" />
|
|
||||||
<circle class="track" cx="91" cy="91" r="55" stroke-width="14" />
|
|
||||||
<circle cx="91" cy="91" r="55" fill="none" stroke="#38bdf8" stroke-width="14" stroke-linecap="round" stroke-dasharray="255 346" transform="rotate(-90 91 91)" />
|
|
||||||
<text x="91" y="84" text-anchor="middle" class="tiny">Turn</text>
|
|
||||||
<text x="91" y="116" text-anchor="middle" class="label" font-size="30">0:42</text>
|
|
||||||
<circle class="arrow-button" cx="22" cy="22" r="16" />
|
|
||||||
<path class="arrow" d="M29 29 L16 16 M16 16 L25 16 M16 16 L16 25" />
|
|
||||||
|
|
||||||
<!-- This expansion begins exactly where the pod ends and continues directly into the top edge. -->
|
|
||||||
<path class="expansion" d="M190 0 H486 V50 Q486 78 458 78 H190 Z" />
|
|
||||||
<rect x="210" y="20" width="200" height="38" rx="8" fill="#7c3aed" opacity="0.72" />
|
|
||||||
<text x="310" y="45" text-anchor="middle" class="label" font-size="18">Rover name</text>
|
|
||||||
<circle class="arrow-button" cx="458" cy="39" r="14" />
|
|
||||||
<path class="arrow" d="M458 46 L458 32 M458 32 L452 38 M458 32 L464 38" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- Top-right pod and both expansions form one continuous edge-mounted cluster. -->
|
|
||||||
<g filter="url(#shadow)">
|
|
||||||
<path class="pod" d="M1010 0 H1200 V190 H1076 Q1010 190 1010 124 Z" />
|
|
||||||
<circle cx="1105" cy="91" r="59" fill="none" stroke="#334155" stroke-width="13" />
|
|
||||||
<circle cx="1105" cy="91" r="59" fill="none" stroke="url(#battery)" stroke-width="13" stroke-linecap="round" stroke-dasharray="300 371" transform="rotate(-90 1105 91)" />
|
|
||||||
<circle cx="1105" cy="91" r="42" fill="none" stroke="#334155" stroke-width="7" />
|
|
||||||
<circle cx="1105" cy="91" r="42" fill="none" stroke="#f59e0b" stroke-width="7" stroke-linecap="round" stroke-dasharray="112 264" transform="rotate(-90 1105 91)" />
|
|
||||||
<text x="1105" y="101" text-anchor="middle" class="label" font-size="30">81%</text>
|
|
||||||
<circle class="arrow-button" cx="1178" cy="22" r="16" />
|
|
||||||
<path class="arrow" d="M1171 29 L1184 16 M1184 16 L1175 16 M1184 16 L1184 25" />
|
|
||||||
|
|
||||||
<!-- Left expansion is attached to the pod at x=1010 and touches the top video edge. -->
|
|
||||||
<path class="expansion" d="M690 0 H1010 V78 H718 Q690 78 690 50 Z" />
|
|
||||||
<text x="718" y="31" class="label" font-size="18">Dock assist</text>
|
|
||||||
<text x="718" y="57" class="small">Dock rover</text>
|
|
||||||
<rect x="912" y="24" width="38" height="30" rx="6" fill="#312e81" stroke="#a5b4fc" />
|
|
||||||
<text x="931" y="45" text-anchor="middle" class="label" font-size="14">G</text>
|
|
||||||
<circle class="arrow-button" cx="980" cy="39" r="14" />
|
|
||||||
<path class="arrow" d="M980 46 L980 32 M980 32 L974 38 M980 32 L986 38" />
|
|
||||||
|
|
||||||
<!-- Lower expansion shares the pod's bottom edge and flows directly into the right edge. -->
|
|
||||||
<path class="expansion" d="M930 190 H1200 V420 H996 Q930 420 930 354 Z" />
|
|
||||||
<text x="958" y="225" class="label" font-size="18">Advanced power</text>
|
|
||||||
<text x="958" y="255" class="small">Voltage</text>
|
|
||||||
<rect x="958" y="266" width="214" height="8" rx="2" fill="#334155" />
|
|
||||||
<rect x="958" y="266" width="160" height="8" rx="2" fill="#38bdf8" />
|
|
||||||
<text x="958" y="306" class="small">Current</text>
|
|
||||||
<rect x="958" y="317" width="214" height="8" rx="2" fill="#334155" />
|
|
||||||
<rect x="958" y="317" width="90" height="8" rx="2" fill="#f59e0b" />
|
|
||||||
<text x="958" y="359" class="tiny">Computer 54 C</text>
|
|
||||||
<text x="958" y="383" class="tiny">Wi-Fi -58 dBm</text>
|
|
||||||
<circle class="arrow-button" cx="1174" cy="216" r="14" />
|
|
||||||
<path class="arrow" d="M1167 216 L1181 216 M1181 216 L1175 210 M1181 216 L1175 222" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- Bottom-left pod flows into the left and bottom edges with a compact triangular control group. -->
|
|
||||||
<g filter="url(#shadow)">
|
|
||||||
<path class="pod" d="M0 680 H220 Q300 680 300 760 V900 H0 Z" />
|
|
||||||
<circle cx="68" cy="758" r="38" fill="#172554" stroke="#60a5fa" stroke-width="2" />
|
|
||||||
<text x="68" y="754" text-anchor="middle" class="label" font-size="24">☀</text>
|
|
||||||
<text x="68" y="779" text-anchor="middle" class="tiny">E</text>
|
|
||||||
<circle cx="102" cy="850" r="38" fill="#3b2f0b" stroke="#facc15" stroke-width="2" />
|
|
||||||
<text x="102" y="846" text-anchor="middle" class="label" font-size="24">⌁</text>
|
|
||||||
<text x="102" y="871" text-anchor="middle" class="tiny">R</text>
|
|
||||||
<circle cx="208" cy="798" r="57" fill="#3f1d2e" stroke="#fb7185" stroke-width="3" />
|
|
||||||
<text x="208" y="794" text-anchor="middle" class="label" font-size="25">Horn</text>
|
|
||||||
<text x="208" y="823" text-anchor="middle" class="tiny">H</text>
|
|
||||||
<circle class="arrow-button" cx="252" cy="754" r="14" />
|
|
||||||
<path class="arrow" d="M246 754 L258 754 M258 754 L253 749 M258 754 L253 759" />
|
|
||||||
<circle class="arrow-button" cx="22" cy="878" r="16" />
|
|
||||||
<path class="arrow" d="M29 871 L16 884 M16 884 L25 884 M16 884 L16 875" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- Bottom-right pod contains a circular tilt slider rather than a horizontal or pill track. -->
|
|
||||||
<g filter="url(#shadow)">
|
|
||||||
<path class="pod" d="M840 900 V760 Q840 680 920 680 H1200 V900 Z" />
|
|
||||||
<text x="1168" y="714" text-anchor="end" class="label" font-size="18">Camera tilt</text>
|
|
||||||
<circle class="track" cx="1030" cy="800" r="76" stroke-width="13" />
|
|
||||||
<circle cx="1030" cy="800" r="76" fill="none" stroke="#38bdf8" stroke-width="13" stroke-linecap="round" stroke-dasharray="285 478" transform="rotate(140 1030 800)" />
|
|
||||||
<circle cx="976" cy="746" r="13" fill="#e0f2fe" stroke="#0284c7" stroke-width="4" />
|
|
||||||
<text x="1030" y="808" text-anchor="middle" class="label" font-size="25">-12.5°</text>
|
|
||||||
<text x="1030" y="832" text-anchor="middle" class="tiny">Click for zero</text>
|
|
||||||
<circle cx="948" cy="838" r="22" fill="#1e3a8a" stroke="#93c5fd" />
|
|
||||||
<text x="948" y="844" text-anchor="middle" class="label" font-size="14">J</text>
|
|
||||||
<circle cx="1112" cy="838" r="22" fill="#1e3a8a" stroke="#93c5fd" />
|
|
||||||
<text x="1112" y="844" text-anchor="middle" class="label" font-size="14">U</text>
|
|
||||||
<circle class="arrow-button" cx="1178" cy="878" r="16" />
|
|
||||||
<path class="arrow" d="M1171 871 L1184 884 M1184 884 L1175 884 M1184 884 L1184 875" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- Immediate sensor overlays remain separate and are shown only as faint context here. -->
|
|
||||||
<path d="M360 900 Q600 808 840 900" fill="none" stroke="#ef4444" stroke-width="13" stroke-linecap="round" opacity="0.25" />
|
|
||||||
<path d="M410 886 Q600 820 790 886" fill="none" stroke="#22c55e" stroke-width="5" stroke-dasharray="12 10" opacity="0.4" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 9.0 KiB |
@@ -1,75 +0,0 @@
|
|||||||
- start work on new better ui layout, using components that already exist when possible
|
|
||||||
- centered rover video, full screen height
|
|
||||||
- rover HUD contains small but expandable rover telemetry UI and vis
|
|
||||||
- make newgen folder for new HUD elements. reuse old elements where possible
|
|
||||||
- make all new hud elements small and clean
|
|
||||||
- every hud element:
|
|
||||||
- is a nice small translucent thing with text icons or both
|
|
||||||
- can be expanded to show more relavent information
|
|
||||||
- is consistent. maybe make a reusable thing for this
|
|
||||||
- some specific hud elements:
|
|
||||||
- top bar:
|
|
||||||
- battery percentage that goes red and flashes and such
|
|
||||||
- turns hud that shows people in queue
|
|
||||||
- big in the middle
|
|
||||||
- left and right sides
|
|
||||||
- wheel drop indicators that show up when wheel drop is happening
|
|
||||||
- overcurrent and battery warnings
|
|
||||||
- bottom section:
|
|
||||||
- sensor elements that show up only when the sensor is "happening"
|
|
||||||
- bumpers
|
|
||||||
- front IR proximity sensors
|
|
||||||
- two sidebars
|
|
||||||
- sidebars contain all the stuff that isnt the rover
|
|
||||||
- left
|
|
||||||
- idk
|
|
||||||
- right
|
|
||||||
- chat, users, rovers list, and replay sources
|
|
||||||
- everything involving the rover is a video HUD, everything external is in the sidebars
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## section 2
|
|
||||||
|
|
||||||
There will be corner mounted (one pod in each corner of the video), rounded pods in the HUD, which will contain gauges and controls for the rover
|
|
||||||
|
|
||||||
These pods will be collapsible, with a corner mounted arrow. the arrow points towards the corner when the pod is out, and points out of the corner when the pod is hidden.
|
|
||||||
|
|
||||||
There can also be "pod expansions" that will be in the corner of the pod and the side of the video. These are also collapsible, but they collapse into the side of the video that they are touching, instead of collapsing into the corner, with the same style arrow button as the pods.
|
|
||||||
|
|
||||||
For example, a pod in the top left is open. This pod has an expansion to it's right that is also open. I can collapse the pod into the corner, the expansion stays, it gets moved into the top left corner where the pod was.
|
|
||||||
|
|
||||||
- corner pods:
|
|
||||||
- top left
|
|
||||||
- pod
|
|
||||||
- turns timer
|
|
||||||
- round gauge circle that ticks down with time
|
|
||||||
- inside it, is the turn countdown
|
|
||||||
- this pod goes away when theres nothing to count
|
|
||||||
- right of pod expansion
|
|
||||||
- rover name with colored background
|
|
||||||
- expanded by default
|
|
||||||
- top right
|
|
||||||
- pod
|
|
||||||
- round rover battery bar gauge, based off how battery bar looks
|
|
||||||
- concentric to this bar is an unlabeled current gauge, styled after the current bar that the top down map contains
|
|
||||||
- inside the circle, is the battery percentage.
|
|
||||||
- left of pod expansion
|
|
||||||
- dock assist button and keybind
|
|
||||||
- expanded by default
|
|
||||||
- below pod expansion
|
|
||||||
- combined advanced power view for the roomba with other info from rover host stats
|
|
||||||
- bottom left
|
|
||||||
- pod
|
|
||||||
- has circular buttons for laser, horn, and headlight
|
|
||||||
- each button is a related icon and the keybind label for the feature
|
|
||||||
- arranged nicely
|
|
||||||
- horn button is larger, and contains an arrow to open the horn settings menu
|
|
||||||
- this pod disappears when none of these things are enabled
|
|
||||||
- if one of the button's features is not enabled, that button should go away
|
|
||||||
- bottom right
|
|
||||||
- pod
|
|
||||||
- rounded camera tilt slider, with keybind label on each end for up / down
|
|
||||||
- in the area inside the slider, show the tilt degrees
|
|
||||||
- clicking the degrees label should set camera tilt to 0
|
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Pi Deployment Guide
|
||||||
|
|
||||||
|
## Tooling
|
||||||
|
|
||||||
|
Fedora 43:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dnf install golang libgpiod
|
||||||
|
```
|
||||||
|
|
||||||
|
Cross-compiling roverd for Pi Zero 2 W (ARMv7):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd pi/roverd
|
||||||
|
mkdir -p ../../dist
|
||||||
|
make pi-build
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary is placed in `dist/roverd` (relative to the repo root).
|
||||||
|
|
||||||
|
### Dummy rover build (for local testing)
|
||||||
|
|
||||||
|
If you need extra “virtual” rovers on your laptop/CI box, build the dummy binary:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd pi/roverd
|
||||||
|
mkdir -p ../../dist
|
||||||
|
make dummy
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the resulting `dist/roverd-dummy` on any machine; it will connect to the server, stream fake Group 100 sensor data, and log drive commands so you can test multi-rover features without additional hardware.
|
||||||
|
|
||||||
|
## Automated installation (recommended)
|
||||||
|
|
||||||
|
Install the Raspberry Pi camera helpers (Bookworm ships them):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libcamera-apps
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the binary (and repo) are on the Pi, run the helper script from the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/MultiRoombaRover
|
||||||
|
sudo ./pi/install_roverd.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
What the script does:
|
||||||
|
|
||||||
|
- creates the `roverd` service account (dialout/gpio/video groups) if missing and installs `/usr/local/bin/roverd`
|
||||||
|
- copies `pi/roverd/roverd.sample.yaml` to `/etc/roverd.yaml` if the file is absent (existing configs are left untouched)
|
||||||
|
- installs/enables the `roverd.service` systemd unit, restarting it automatically when a config already exists
|
||||||
|
- installs `/usr/local/bin/video-publisher`, drops `video-publisher.service`, and enables it so video is published automatically on boot
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
|
||||||
|
| Flag | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `-b PATH` | use a different roverd binary (defaults to `dist/roverd`) |
|
||||||
|
| `-c PATH` | seed `/etc/roverd.yaml` from another template |
|
||||||
|
|
||||||
|
If the script installs the sample config, it will remind you to edit `/etc/roverd.yaml` before manually restarting the service: set `name`, `serverUrl`, serial device, BRC pin, battery thresholds, and optionally override `media.publishUrl`. When left blank, roverd automatically publishes to `srt://<server-host>:9000?streamid=#!::r=<name>,m=publish…` (the host comes from `serverUrl`). If your reverse proxy adds prefixes (like `/video/<name>`), use its rewrite options so the rover keeps publishing to plain `<name>`.
|
||||||
|
|
||||||
|
Re-run `pi/install_roverd.sh` any time you pull updates—the script overwrites the roverd + video-publisher binaries and drops the latest systemd units so the only configuration you ever touch manually is `/etc/roverd.yaml`. `roverd` rewrites `/var/lib/roverd/video.env` on startup, so no other files need editing.
|
||||||
|
## Manual installation
|
||||||
|
|
||||||
|
1. Copy the binary and config:
|
||||||
|
```bash
|
||||||
|
sudo useradd -r -s /usr/sbin/nologin roverd || true
|
||||||
|
sudo install -o roverd -g roverd -m 0755 dist/roverd /usr/local/bin/roverd
|
||||||
|
sudo install -o roverd -g roverd -m 0640 pi/roverd/roverd.sample.yaml /etc/roverd.yaml
|
||||||
|
```
|
||||||
|
Adjust `/etc/roverd.yaml` for each rover: `name`, `serverUrl` (e.g. `ws://control-server:8080/rover`), serial port path, battery thresholds, GPIO pin for BRC, and (if needed) the media `publishUrl` override. Otherwise, roverd derives `srt://<server-host>:9000?streamid=#!::r=<name>,m=publish&latency=20&mode=caller&transtype=live&pkt_size=1316` based on the `serverUrl`; keep proxy-only path prefixes out of this URL so every rover keeps the same simple stream name.
|
||||||
|
|
||||||
|
2. Install the systemd unit:
|
||||||
|
```bash
|
||||||
|
sudo install -m 0644 pi/systemd/roverd.service /etc/systemd/system/roverd.service
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now roverd.service
|
||||||
|
```
|
||||||
|
|
||||||
|
`roverd` requires access to `/dev/ttyAMA0` and `/dev/gpiochip*`; keeping it under its own user ensures the rest of the system stays isolated—just make sure the account belongs to the `dialout` and `gpio` groups so it can reach the UART and libgpiod.
|
||||||
|
The video publisher needs read access to the camera devices (`/dev/media*`, `/dev/video*`), so the install script adds the `roverd` service account to the `video` group; if you created the user manually, make sure it belongs to `video`.
|
||||||
|
**BRC note:** configure `brc.gpioPin` (and `brc.gpioChip` if you’re not using `gpiochip0`) and ensure the `roverd` user has permission to toggle that line—no root privileges are required anymore.
|
||||||
|
If you set `media.manage: true` in `/etc/roverd.yaml`, make sure the `roverd` service account can invoke `systemctl <action> <media.service>` (the installer wires `video-publisher.service` to run as `roverd`, so no sudo tweaks are required unless you rename it).
|
||||||
|
|
||||||
|
## Video publisher details
|
||||||
|
|
||||||
|
The `video-publisher.service` unit runs `/usr/local/bin/video-publisher`, piping `rpicam-vid`/`libcamera-vid` straight into the stock FFmpeg package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rpicam-vid (or libcamera-vid) --inline --timeout 0 --width=WIDTH --height=HEIGHT \
|
||||||
|
--framerate=FPS --bitrate=BITRATE --codec h264 --profile baseline --output - \
|
||||||
|
| ffmpeg -hide_banner -loglevel warning -fflags nobuffer \
|
||||||
|
-f h264 -i pipe:0 -c:v copy -an -flush_packets 1 -f mpegts $PUBLISH_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
`/var/lib/roverd/video.env` carries all tunables (`PUBLISH_URL`, resolution, FPS, bitrate). `roverd` rewrites the file whenever you restart it or hit the “Restart Camera” button so the publisher always inherits the correct rover ID + bitrate knobs. Because we now use the distro FFmpeg build with SRT enabled, the installer is much faster—no custom compile steps.
|
||||||
|
Use `sudo systemctl status video-publisher` to watch logs; the unit auto-restarts whenever the connection drops or FFmpeg exits with an error.
|
||||||
|
|
||||||
|
## Server + UI
|
||||||
|
|
||||||
|
From the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
npm install
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
This launches the HTTP server (serving the barebones UI) and the rover WebSocket endpoint at `ws://<server>:8080/rover`. The UI expects `roverd` instances to send `hello` frames so it can populate the rover list. Use the mode buttons to emit Start/Safe/Full/Passive/Dock commands (they send the raw OI opcode bytes), tap the sensor toggle to request Group 100 streaming, and use WASD for drive testing; the UI emits Drive Direct commands ~8 times per second, so the rover sees them immediately.
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
# Simple spectator bot
|
|
||||||
|
|
||||||
A spectator bot connects to the rover server with Socket.IO. It can receive the current session, read chat, and send messages that are visually tagged as bot messages.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
Create a small Node.js project and install the Socket.IO client:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install socket.io-client
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example bot
|
|
||||||
|
|
||||||
Create `bot.js`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { io } from 'socket.io-client';
|
|
||||||
|
|
||||||
// Replace this with the public URL of the MultiRoombaRover server.
|
|
||||||
const socket = io('https://your-rover-server.example', {
|
|
||||||
// Match the transports supported by the server while retaining polling as a
|
|
||||||
// fallback for networks or proxies that do not allow WebSocket connections.
|
|
||||||
transports: ['websocket', 'polling'],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Socket.IO acknowledgements use callbacks. This small wrapper turns them into
|
|
||||||
// promises so setup failures and rejected chat messages are easy to handle.
|
|
||||||
function emitWithAck(event, payload) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
socket.emit(event, payload, (response = {}) => {
|
|
||||||
if (response.error) {
|
|
||||||
reject(new Error(response.error));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on('connect', async () => {
|
|
||||||
console.log('Connected:', socket.id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Set the name that will appear beside this connection and its messages.
|
|
||||||
await emitWithAck('nickname:set', {
|
|
||||||
nickname: 'My spectator bot',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ask the server to make this passive connection a spectator. Performing
|
|
||||||
// this after every connection also restores the role after a reconnect.
|
|
||||||
await emitWithAck('session:setRole', {
|
|
||||||
role: 'spectator',
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Connected as a spectator');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Spectator setup failed:', error.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Each session:sync event is a complete current session snapshot. Replace any
|
|
||||||
// previously stored session with this object instead of merging snapshots.
|
|
||||||
socket.on('session:sync', (session) => {
|
|
||||||
console.log('Session:', session);
|
|
||||||
});
|
|
||||||
|
|
||||||
// chat:init contains the recent chat history available when the bot connects.
|
|
||||||
socket.on('chat:init', (messages) => {
|
|
||||||
console.log('Recent chat:', messages);
|
|
||||||
});
|
|
||||||
|
|
||||||
// chat:message fires whenever a new message is broadcast, including messages
|
|
||||||
// sent by this bot itself.
|
|
||||||
socket.on('chat:message', (message) => {
|
|
||||||
console.log(`${message.nickname || 'Unknown'}: ${message.text}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('disconnect', (reason) => {
|
|
||||||
console.log('Disconnected:', reason);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setting bot to true adds the normal bot tag to the displayed chat message.
|
|
||||||
// It does not grant the connection any additional permissions.
|
|
||||||
function sendBotMessage(text) {
|
|
||||||
return emitWithAck('chat:send', {
|
|
||||||
text,
|
|
||||||
bot: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send one example message after the connection has had time to finish setup.
|
|
||||||
// A real bot would call sendBotMessage from its own message-handling logic.
|
|
||||||
setTimeout(() => {
|
|
||||||
sendBotMessage('Hello from my spectator bot!').catch((error) => {
|
|
||||||
console.error('Message failed:', error.message);
|
|
||||||
});
|
|
||||||
}, 5000);
|
|
||||||
```
|
|
||||||
|
|
||||||
Run it with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node bot.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Events used
|
|
||||||
|
|
||||||
- `nickname:set` sets the bot's visible nickname.
|
|
||||||
- `session:setRole` changes the connection to a spectator.
|
|
||||||
- `session:sync` provides the latest complete session state.
|
|
||||||
- `chat:init` provides recent chat history after connecting.
|
|
||||||
- `chat:message` provides new chat messages.
|
|
||||||
- `chat:send` sends a chat message. Include `bot: true` to give it the bot tag.
|
|
||||||
|
|
||||||
The server can reject spectator access or a chat message. Always check the acknowledgement callback, as the example does, so those errors are not silently ignored.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "RoverPeripheralFirmata",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "Self-describing Firmata controls for MultiRoombaRover ESP32 peripherals",
|
|
||||||
"frameworks": "arduino",
|
|
||||||
"platforms": "espressif32",
|
|
||||||
"dependencies": {
|
|
||||||
"bblanchon/ArduinoJson": "^7.4.2",
|
|
||||||
"madhephaestus/ESP32Servo": "^3.0.8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,511 +0,0 @@
|
|||||||
#include "RoverPeripheralFirmata.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr byte kPeripheralFeature = 0x01;
|
|
||||||
constexpr byte kDescribeOperation = 0x00;
|
|
||||||
constexpr byte kDescriptionOperation = 0x01;
|
|
||||||
constexpr byte kControlOperation = 0x02;
|
|
||||||
|
|
||||||
const char* buttonModeName(ButtonMode mode) {
|
|
||||||
return mode == ButtonMode::Toggle ? "toggle" : "momentary";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* outputTypeName(uint8_t value) {
|
|
||||||
switch (value) {
|
|
||||||
case 0:
|
|
||||||
return "servo";
|
|
||||||
case 1:
|
|
||||||
return "pwm";
|
|
||||||
case 2:
|
|
||||||
return "digital";
|
|
||||||
default:
|
|
||||||
return "custom";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
RoverPeripheralFirmata* RoverPeripheralFirmata::instance_ = nullptr;
|
|
||||||
|
|
||||||
RoverPeripheralFirmata::RoverPeripheralFirmata(const String& name) : name_(name) {}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::validateControlIdentity(const String& id, const String& name) const {
|
|
||||||
if (id.length() == 0 || name.length() == 0) {
|
|
||||||
// Registration errors are programmer errors discovered during setup. A
|
|
||||||
// hard stop is preferable to advertising a partially usable device whose
|
|
||||||
// behavior depends on which malformed control the driver touches first.
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
for (const ControlRegistration& existing : controls_) {
|
|
||||||
if (existing.id == id) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::validateRange(const String& id, int minimum, int maximum) const {
|
|
||||||
if (id.length() == 0 || minimum > maximum) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::addServoSlider(const SliderControlConfig& config, const FirmataServoOutput& output) {
|
|
||||||
validateControlIdentity(config.id, config.name);
|
|
||||||
validateRange(config.id, config.minimum, config.maximum);
|
|
||||||
ControlRegistration control;
|
|
||||||
control.id = config.id;
|
|
||||||
control.name = config.name;
|
|
||||||
control.type = ControlType::Slider;
|
|
||||||
control.output = OutputType::Servo;
|
|
||||||
control.minimum = config.minimum;
|
|
||||||
control.maximum = config.maximum;
|
|
||||||
control.pin = output.pin;
|
|
||||||
controls_.push_back(control);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::addPwmSlider(const SliderControlConfig& config, const FirmataPwmOutput& output) {
|
|
||||||
validateControlIdentity(config.id, config.name);
|
|
||||||
validateRange(config.id, config.minimum, config.maximum);
|
|
||||||
ControlRegistration control;
|
|
||||||
control.id = config.id;
|
|
||||||
control.name = config.name;
|
|
||||||
control.type = ControlType::Slider;
|
|
||||||
control.output = OutputType::Pwm;
|
|
||||||
control.minimum = config.minimum;
|
|
||||||
control.maximum = config.maximum;
|
|
||||||
control.pin = output.pin;
|
|
||||||
controls_.push_back(control);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::addDigitalButton(const ButtonControlConfig& config, const FirmataDigitalOutput& output) {
|
|
||||||
validateControlIdentity(config.id, config.name);
|
|
||||||
ControlRegistration control;
|
|
||||||
control.id = config.id;
|
|
||||||
control.name = config.name;
|
|
||||||
control.type = ControlType::Button;
|
|
||||||
control.output = OutputType::Digital;
|
|
||||||
control.buttonMode = config.mode;
|
|
||||||
control.pin = output.pin;
|
|
||||||
control.polarity = output.polarity;
|
|
||||||
controls_.push_back(control);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::addSlider(const SliderControlConfig& config, SliderCallback callback) {
|
|
||||||
validateControlIdentity(config.id, config.name);
|
|
||||||
validateRange(config.id, config.minimum, config.maximum);
|
|
||||||
ControlRegistration control;
|
|
||||||
control.id = config.id;
|
|
||||||
control.name = config.name;
|
|
||||||
control.type = ControlType::Slider;
|
|
||||||
control.output = OutputType::Custom;
|
|
||||||
control.minimum = config.minimum;
|
|
||||||
control.maximum = config.maximum;
|
|
||||||
control.sliderCallback = callback;
|
|
||||||
controls_.push_back(control);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::addButton(const ButtonControlConfig& config, ButtonCallback callback) {
|
|
||||||
validateControlIdentity(config.id, config.name);
|
|
||||||
ControlRegistration control;
|
|
||||||
control.id = config.id;
|
|
||||||
control.name = config.name;
|
|
||||||
control.type = ControlType::Button;
|
|
||||||
control.output = OutputType::Custom;
|
|
||||||
control.buttonMode = config.mode;
|
|
||||||
control.buttonCallback = callback;
|
|
||||||
controls_.push_back(control);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::addNumber(const NumberControlConfig& config, NumberCallback callback) {
|
|
||||||
validateControlIdentity(config.id, config.name);
|
|
||||||
validateRange(config.id, config.minimum, config.maximum);
|
|
||||||
ControlRegistration control;
|
|
||||||
control.id = config.id;
|
|
||||||
control.name = config.name;
|
|
||||||
control.type = ControlType::Number;
|
|
||||||
control.output = OutputType::Custom;
|
|
||||||
control.minimum = config.minimum;
|
|
||||||
control.maximum = config.maximum;
|
|
||||||
control.numberCallback = callback;
|
|
||||||
controls_.push_back(control);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::addText(const TextControlConfig& config, TextCallback callback) {
|
|
||||||
validateControlIdentity(config.id, config.name);
|
|
||||||
if (config.maximumLength == 0) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
ControlRegistration control;
|
|
||||||
control.id = config.id;
|
|
||||||
control.name = config.name;
|
|
||||||
control.type = ControlType::Text;
|
|
||||||
control.output = OutputType::Custom;
|
|
||||||
control.maximumLength = config.maximumLength;
|
|
||||||
control.textCallback = callback;
|
|
||||||
controls_.push_back(control);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::addRoverCameraServo(const RoverCameraServoConfig& config) {
|
|
||||||
cameraServo_ = config;
|
|
||||||
hasCameraServo_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::addRoverHeadlight(const RoverDigitalOutputConfig& config) {
|
|
||||||
headlight_ = config;
|
|
||||||
hasHeadlight_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::addRoverLaser(const RoverDigitalOutputConfig& config) {
|
|
||||||
laser_ = config;
|
|
||||||
hasLaser_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::begin(FirmataExt& extension) {
|
|
||||||
if (instance_ != nullptr && instance_ != this) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
instance_ = this;
|
|
||||||
extension.addFeature(*this);
|
|
||||||
|
|
||||||
// Discovery uses Firmata's standard REPORT_FIRMWARE query to distinguish a
|
|
||||||
// rover peripheral from unrelated Firmata devices. The helper owns this
|
|
||||||
// identity so every sketch gets it without repeating protocol boilerplate.
|
|
||||||
Firmata.setFirmwareNameAndVersion("RoverPeripheralFirmata", 1, 0);
|
|
||||||
|
|
||||||
// SET_DIGITAL_PIN_VALUE is a fixed Firmata command rather than SysEx, so it
|
|
||||||
// cannot travel through FirmataFeature::handleSysex. Firmata exposes one
|
|
||||||
// callback for it and this peripheral owns the standard output implementation.
|
|
||||||
Firmata.attach(SET_DIGITAL_PIN_VALUE, digitalPinValueCallback);
|
|
||||||
Firmata.attach(SYSTEM_RESET, systemResetCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::update() {
|
|
||||||
// Custom callbacks execute synchronously from Firmata's parser for now. This
|
|
||||||
// method intentionally remains available so future non-blocking peripheral
|
|
||||||
// work can be serviced without changing the sketch's main loop shape.
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::handleCapability(byte pin) {
|
|
||||||
if (!IS_PIN_DIGITAL(pin)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The peripheral supports the output modes roverd may select. Capability
|
|
||||||
// reporting stays standard Firmata, so the Linux probe can also inspect it
|
|
||||||
// with any other conforming client.
|
|
||||||
Firmata.write(PIN_MODE_OUTPUT);
|
|
||||||
Firmata.write(1);
|
|
||||||
if (IS_PIN_PWM(pin)) {
|
|
||||||
Firmata.write(PIN_MODE_PWM);
|
|
||||||
Firmata.write(DEFAULT_PWM_RESOLUTION);
|
|
||||||
}
|
|
||||||
Firmata.write(PIN_MODE_SERVO);
|
|
||||||
Firmata.write(14);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean RoverPeripheralFirmata::handlePinMode(byte pin, int mode) {
|
|
||||||
if (pin >= TOTAL_PINS || !IS_PIN_DIGITAL(pin)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A pin can only have one active hardware generator. Detaching a previous
|
|
||||||
// servo before switching modes prevents it from continuing to pulse after a
|
|
||||||
// later digital or PWM configuration takes ownership of the pin.
|
|
||||||
if (mode != PIN_MODE_SERVO) {
|
|
||||||
detachServo(pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (mode) {
|
|
||||||
case PIN_MODE_OUTPUT:
|
|
||||||
pinMode(PIN_TO_DIGITAL(pin), OUTPUT);
|
|
||||||
digitalWrite(PIN_TO_DIGITAL(pin), LOW);
|
|
||||||
Firmata.setPinState(pin, 0);
|
|
||||||
return true;
|
|
||||||
case PIN_MODE_PWM:
|
|
||||||
if (!IS_PIN_PWM(pin)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
pinMode(PIN_TO_PWM(pin), OUTPUT);
|
|
||||||
analogWrite(PIN_TO_PWM(pin), 0);
|
|
||||||
Firmata.setPinState(pin, 0);
|
|
||||||
return true;
|
|
||||||
case PIN_MODE_SERVO:
|
|
||||||
attachServo(pin);
|
|
||||||
Firmata.setPinState(pin, 0);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean RoverPeripheralFirmata::handleSysex(byte command, byte argc, byte* argv) {
|
|
||||||
if (command == kPeripheralFeature) {
|
|
||||||
if (argc == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (argv[0] == kDescribeOperation) {
|
|
||||||
buildAndSendDescription();
|
|
||||||
} else if (argv[0] == kControlOperation) {
|
|
||||||
dispatchCustomControl(argc, argv);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command == SERVO_CONFIG && argc >= 5) {
|
|
||||||
const byte pin = argv[0];
|
|
||||||
const int minimumPulse = argv[1] | (argv[2] << 7);
|
|
||||||
const int maximumPulse = argv[3] | (argv[4] << 7);
|
|
||||||
if (pin < TOTAL_PINS && IS_PIN_DIGITAL(pin)) {
|
|
||||||
Firmata.setPinMode(pin, PIN_MODE_SERVO);
|
|
||||||
attachServo(pin, minimumPulse, maximumPulse);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command == EXTENDED_ANALOG && argc >= 2) {
|
|
||||||
const byte pin = argv[0];
|
|
||||||
if (pin >= TOTAL_PINS) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int value = 0;
|
|
||||||
// Extended analog values contain a variable number of seven-bit chunks.
|
|
||||||
// Reassembling every received chunk keeps servo angles and PWM values fully
|
|
||||||
// compatible with normal Firmata clients rather than assuming eight bits.
|
|
||||||
for (byte index = 1; index < argc && index <= 4; ++index) {
|
|
||||||
value |= static_cast<int>(argv[index]) << (7 * (index - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
const byte mode = Firmata.getPinMode(pin);
|
|
||||||
if (mode == PIN_MODE_PWM && IS_PIN_PWM(pin)) {
|
|
||||||
analogWrite(PIN_TO_PWM(pin), value);
|
|
||||||
Firmata.setPinState(pin, value);
|
|
||||||
} else if (mode == PIN_MODE_SERVO && servos_[pin] != nullptr) {
|
|
||||||
servos_[pin]->write(value);
|
|
||||||
Firmata.setPinState(pin, value);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::reset() {
|
|
||||||
for (byte pin = 0; pin < TOTAL_PINS; ++pin) {
|
|
||||||
detachServo(pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Built-in role defaults are applied on Firmata reset as well as boot. This
|
|
||||||
// makes reconnecting a client deterministic without creating a second state
|
|
||||||
// model on the ESP32.
|
|
||||||
if (hasHeadlight_) {
|
|
||||||
pinMode(headlight_.pin, OUTPUT);
|
|
||||||
const bool physicalHigh = headlight_.initiallyOn != (headlight_.polarity == OutputPolarity::ActiveLow);
|
|
||||||
writeDigitalPin(headlight_.pin, physicalHigh);
|
|
||||||
}
|
|
||||||
if (hasLaser_) {
|
|
||||||
pinMode(laser_.pin, OUTPUT);
|
|
||||||
const bool physicalHigh = laser_.initiallyOn != (laser_.polarity == OutputPolarity::ActiveLow);
|
|
||||||
writeDigitalPin(laser_.pin, physicalHigh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::buildAndSendDescription() {
|
|
||||||
JsonDocument document;
|
|
||||||
document["name"] = name_;
|
|
||||||
|
|
||||||
if (hasCameraServo_ || hasHeadlight_ || hasLaser_) {
|
|
||||||
JsonObject roverControls = document["roverControls"].to<JsonObject>();
|
|
||||||
if (hasCameraServo_) {
|
|
||||||
JsonObject servo = roverControls["cameraServo"].to<JsonObject>();
|
|
||||||
servo["pin"] = cameraServo_.pin;
|
|
||||||
servo["minimumAngleDegrees"] = cameraServo_.minimumAngleDegrees;
|
|
||||||
servo["maximumAngleDegrees"] = cameraServo_.maximumAngleDegrees;
|
|
||||||
servo["homeAngleDegrees"] = cameraServo_.homeAngleDegrees;
|
|
||||||
servo["nudgeDegrees"] = cameraServo_.nudgeDegrees;
|
|
||||||
servo["minimumPulseMicroseconds"] = cameraServo_.minimumPulseMicroseconds;
|
|
||||||
servo["maximumPulseMicroseconds"] = cameraServo_.maximumPulseMicroseconds;
|
|
||||||
servo["allowRawPulse"] = cameraServo_.allowRawPulse;
|
|
||||||
servo["inverted"] = cameraServo_.inverted;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto addDigitalRole = [&roverControls](const char* key, const RoverDigitalOutputConfig& config) {
|
|
||||||
JsonObject role = roverControls[key].to<JsonObject>();
|
|
||||||
role["pin"] = config.pin;
|
|
||||||
role["activeLow"] = config.polarity == OutputPolarity::ActiveLow;
|
|
||||||
role["initiallyOn"] = config.initiallyOn;
|
|
||||||
};
|
|
||||||
if (hasHeadlight_) {
|
|
||||||
addDigitalRole("headlight", headlight_);
|
|
||||||
}
|
|
||||||
if (hasLaser_) {
|
|
||||||
addDigitalRole("laser", laser_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonArray controls = document["controls"].to<JsonArray>();
|
|
||||||
for (const ControlRegistration& registration : controls_) {
|
|
||||||
JsonObject control = controls.add<JsonObject>();
|
|
||||||
control["id"] = registration.id;
|
|
||||||
control["name"] = registration.name;
|
|
||||||
|
|
||||||
switch (registration.type) {
|
|
||||||
case ControlType::Slider:
|
|
||||||
control["type"] = "slider";
|
|
||||||
control["min"] = registration.minimum;
|
|
||||||
control["max"] = registration.maximum;
|
|
||||||
break;
|
|
||||||
case ControlType::Button:
|
|
||||||
control["type"] = "button";
|
|
||||||
control["mode"] = buttonModeName(registration.buttonMode);
|
|
||||||
break;
|
|
||||||
case ControlType::Number:
|
|
||||||
control["type"] = "number";
|
|
||||||
control["min"] = registration.minimum;
|
|
||||||
control["max"] = registration.maximum;
|
|
||||||
break;
|
|
||||||
case ControlType::Text:
|
|
||||||
control["type"] = "text";
|
|
||||||
control["maxLength"] = registration.maximumLength;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject output = control["output"].to<JsonObject>();
|
|
||||||
output["type"] = outputTypeName(static_cast<uint8_t>(registration.output));
|
|
||||||
if (registration.output != OutputType::Custom) {
|
|
||||||
output["pin"] = registration.pin;
|
|
||||||
}
|
|
||||||
if (registration.output == OutputType::Digital && registration.polarity == OutputPolarity::ActiveLow) {
|
|
||||||
output["activeLow"] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String payload;
|
|
||||||
serializeJson(document, payload);
|
|
||||||
|
|
||||||
// ConfigurableFirmata's convenience sendSysex takes a byte-sized raw length.
|
|
||||||
// Descriptions can exceed that, so write the standard framing and each 7-bit
|
|
||||||
// pair directly. This remains one ordinary Firmata SysEx message on the wire.
|
|
||||||
Firmata.startSysex();
|
|
||||||
Firmata.write(kPeripheralFeature);
|
|
||||||
Firmata.write(kDescriptionOperation);
|
|
||||||
for (size_t index = 0; index < payload.length(); ++index) {
|
|
||||||
Firmata.sendValueAsTwo7bitBytes(static_cast<uint8_t>(payload[index]));
|
|
||||||
}
|
|
||||||
Firmata.endSysex();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::dispatchCustomControl(byte argc, byte* argv) {
|
|
||||||
if (argc < 3 || ((argc - 1) % 2) != 0) {
|
|
||||||
Firmata.sendString(F("Invalid rover control payload"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String decoded;
|
|
||||||
decoded.reserve((argc - 1) / 2);
|
|
||||||
for (byte index = 1; index + 1 < argc; index += 2) {
|
|
||||||
if (argv[index + 1] > 1) {
|
|
||||||
Firmata.sendString(F("Invalid rover control encoding"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
decoded += static_cast<char>(argv[index] | (argv[index + 1] << 7));
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonDocument document;
|
|
||||||
if (deserializeJson(document, decoded) != DeserializationError::Ok) {
|
|
||||||
Firmata.sendString(F("Invalid rover control JSON"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String controlID = document["control"].as<String>();
|
|
||||||
for (ControlRegistration& registration : controls_) {
|
|
||||||
if (registration.id != controlID || registration.output != OutputType::Custom) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The registration type is the source of truth for value conversion. This
|
|
||||||
// prevents an unexpected JSON value from silently selecting a different
|
|
||||||
// callback signature or invoking unrelated application behavior.
|
|
||||||
switch (registration.type) {
|
|
||||||
case ControlType::Slider:
|
|
||||||
if (registration.sliderCallback) {
|
|
||||||
registration.sliderCallback(document["value"].as<int>());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ControlType::Button:
|
|
||||||
if (registration.buttonCallback) {
|
|
||||||
registration.buttonCallback(document["value"].as<bool>());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ControlType::Number:
|
|
||||||
if (registration.numberCallback) {
|
|
||||||
registration.numberCallback(document["value"].as<int>());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ControlType::Text:
|
|
||||||
if (registration.textCallback) {
|
|
||||||
String value = document["value"].as<String>();
|
|
||||||
if (value.length() > registration.maximumLength) {
|
|
||||||
value.remove(registration.maximumLength);
|
|
||||||
}
|
|
||||||
registration.textCallback(value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Firmata.sendString(F("Unknown rover control"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::writeDigitalPin(byte pin, bool physicalHigh) {
|
|
||||||
// Standard Firmata digital values represent the electrical pin level. roverd
|
|
||||||
// applies the advertised activeLow mapping before sending a command, keeping
|
|
||||||
// this firmware compatible with raw Firmata clients and avoiding inversion in
|
|
||||||
// two different layers.
|
|
||||||
digitalWrite(PIN_TO_DIGITAL(pin), physicalHigh ? HIGH : LOW);
|
|
||||||
Firmata.setPinState(pin, physicalHigh ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::attachServo(byte pin, int minimumPulseMicroseconds, int maximumPulseMicroseconds) {
|
|
||||||
if (pin >= TOTAL_PINS || !IS_PIN_DIGITAL(pin)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (servos_[pin] == nullptr) {
|
|
||||||
servos_[pin] = new Servo();
|
|
||||||
}
|
|
||||||
if (servos_[pin]->attached()) {
|
|
||||||
servos_[pin]->detach();
|
|
||||||
}
|
|
||||||
if (minimumPulseMicroseconds > 0 && maximumPulseMicroseconds > minimumPulseMicroseconds) {
|
|
||||||
servos_[pin]->attach(PIN_TO_SERVO(pin), minimumPulseMicroseconds, maximumPulseMicroseconds);
|
|
||||||
} else {
|
|
||||||
servos_[pin]->attach(PIN_TO_SERVO(pin));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::detachServo(byte pin) {
|
|
||||||
if (pin >= TOTAL_PINS || servos_[pin] == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (servos_[pin]->attached()) {
|
|
||||||
servos_[pin]->detach();
|
|
||||||
}
|
|
||||||
delete servos_[pin];
|
|
||||||
servos_[pin] = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::digitalPinValueCallback(byte pin, int value) {
|
|
||||||
if (instance_ == nullptr || pin >= TOTAL_PINS || Firmata.getPinMode(pin) != PIN_MODE_OUTPUT) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Polarity is advertised by the peripheral and applied by roverd before this
|
|
||||||
// standard raw pin-level command reaches the ESP32.
|
|
||||||
instance_->writeDigitalPin(pin, value != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoverPeripheralFirmata::systemResetCallback() {
|
|
||||||
if (instance_ != nullptr) {
|
|
||||||
instance_->reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <ConfigurableFirmata.h>
|
|
||||||
#include <ESP32Servo.h>
|
|
||||||
#include <FirmataExt.h>
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
enum class OutputPolarity {
|
|
||||||
ActiveHigh,
|
|
||||||
ActiveLow,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ButtonMode {
|
|
||||||
Toggle,
|
|
||||||
Momentary,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FirmataServoOutput {
|
|
||||||
uint8_t pin = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FirmataPwmOutput {
|
|
||||||
uint8_t pin = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FirmataDigitalOutput {
|
|
||||||
uint8_t pin = 0;
|
|
||||||
OutputPolarity polarity = OutputPolarity::ActiveHigh;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RoverCameraServoConfig {
|
|
||||||
uint8_t pin = 0;
|
|
||||||
float minimumAngleDegrees = -15;
|
|
||||||
float maximumAngleDegrees = 30;
|
|
||||||
float homeAngleDegrees = 0;
|
|
||||||
float nudgeDegrees = 2;
|
|
||||||
uint16_t minimumPulseMicroseconds = 900;
|
|
||||||
uint16_t maximumPulseMicroseconds = 2100;
|
|
||||||
bool allowRawPulse = false;
|
|
||||||
bool inverted = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RoverDigitalOutputConfig {
|
|
||||||
uint8_t pin = 0;
|
|
||||||
OutputPolarity polarity = OutputPolarity::ActiveHigh;
|
|
||||||
bool initiallyOn = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SliderControlConfig {
|
|
||||||
String id;
|
|
||||||
String name;
|
|
||||||
int minimum = 0;
|
|
||||||
int maximum = 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ButtonControlConfig {
|
|
||||||
String id;
|
|
||||||
String name;
|
|
||||||
ButtonMode mode = ButtonMode::Momentary;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NumberControlConfig {
|
|
||||||
String id;
|
|
||||||
String name;
|
|
||||||
int minimum = 0;
|
|
||||||
int maximum = 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TextControlConfig {
|
|
||||||
String id;
|
|
||||||
String name;
|
|
||||||
size_t maximumLength = 32;
|
|
||||||
};
|
|
||||||
|
|
||||||
using SliderCallback = std::function<void(int)>;
|
|
||||||
using ButtonCallback = std::function<void(bool)>;
|
|
||||||
using NumberCallback = std::function<void(int)>;
|
|
||||||
using TextCallback = std::function<void(const String&)>;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RoverPeripheralFirmata is both the sketch-facing registration API and one
|
|
||||||
* ConfigurableFirmata feature. Keeping those responsibilities together gives a
|
|
||||||
* peripheral author one object to configure while still allowing ordinary
|
|
||||||
* Firmata tooling to use digital, PWM, and servo commands on the same stream.
|
|
||||||
*/
|
|
||||||
class RoverPeripheralFirmata : public FirmataFeature {
|
|
||||||
public:
|
|
||||||
explicit RoverPeripheralFirmata(const String& name);
|
|
||||||
|
|
||||||
void addServoSlider(const SliderControlConfig& config, const FirmataServoOutput& output);
|
|
||||||
void addPwmSlider(const SliderControlConfig& config, const FirmataPwmOutput& output);
|
|
||||||
void addDigitalButton(const ButtonControlConfig& config, const FirmataDigitalOutput& output);
|
|
||||||
void addSlider(const SliderControlConfig& config, SliderCallback callback);
|
|
||||||
void addButton(const ButtonControlConfig& config, ButtonCallback callback);
|
|
||||||
void addNumber(const NumberControlConfig& config, NumberCallback callback);
|
|
||||||
void addText(const TextControlConfig& config, TextCallback callback);
|
|
||||||
|
|
||||||
void addRoverCameraServo(const RoverCameraServoConfig& config);
|
|
||||||
void addRoverHeadlight(const RoverDigitalOutputConfig& config);
|
|
||||||
void addRoverLaser(const RoverDigitalOutputConfig& config);
|
|
||||||
|
|
||||||
void begin(FirmataExt& extension);
|
|
||||||
void update();
|
|
||||||
|
|
||||||
// FirmataFeature methods let FirmataExt route standard and custom SysEx
|
|
||||||
// operations through the same parser that owns the serial connection.
|
|
||||||
void handleCapability(byte pin) override;
|
|
||||||
boolean handlePinMode(byte pin, int mode) override;
|
|
||||||
boolean handleSysex(byte command, byte argc, byte* argv) override;
|
|
||||||
void reset() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum class ControlType {
|
|
||||||
Slider,
|
|
||||||
Button,
|
|
||||||
Number,
|
|
||||||
Text,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class OutputType {
|
|
||||||
Servo,
|
|
||||||
Pwm,
|
|
||||||
Digital,
|
|
||||||
Custom,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ControlRegistration {
|
|
||||||
String id;
|
|
||||||
String name;
|
|
||||||
ControlType type;
|
|
||||||
OutputType output;
|
|
||||||
int minimum = 0;
|
|
||||||
int maximum = 0;
|
|
||||||
size_t maximumLength = 0;
|
|
||||||
ButtonMode buttonMode = ButtonMode::Momentary;
|
|
||||||
uint8_t pin = 0;
|
|
||||||
OutputPolarity polarity = OutputPolarity::ActiveHigh;
|
|
||||||
SliderCallback sliderCallback;
|
|
||||||
ButtonCallback buttonCallback;
|
|
||||||
NumberCallback numberCallback;
|
|
||||||
TextCallback textCallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
String name_;
|
|
||||||
std::vector<ControlRegistration> controls_;
|
|
||||||
bool hasCameraServo_ = false;
|
|
||||||
bool hasHeadlight_ = false;
|
|
||||||
bool hasLaser_ = false;
|
|
||||||
RoverCameraServoConfig cameraServo_;
|
|
||||||
RoverDigitalOutputConfig headlight_;
|
|
||||||
RoverDigitalOutputConfig laser_;
|
|
||||||
Servo* servos_[TOTAL_PINS] = {};
|
|
||||||
|
|
||||||
void validateControlIdentity(const String& id, const String& name) const;
|
|
||||||
void validateRange(const String& id, int minimum, int maximum) const;
|
|
||||||
void buildAndSendDescription();
|
|
||||||
void dispatchCustomControl(byte argc, byte* argv);
|
|
||||||
void writeDigitalPin(byte pin, bool enabled);
|
|
||||||
void attachServo(byte pin, int minimumPulseMicroseconds = -1, int maximumPulseMicroseconds = -1);
|
|
||||||
void detachServo(byte pin);
|
|
||||||
|
|
||||||
static RoverPeripheralFirmata* instance_;
|
|
||||||
static void digitalPinValueCallback(byte pin, int value);
|
|
||||||
static void systemResetCallback();
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
[platformio]
|
|
||||||
default_envs = esp32dev
|
|
||||||
|
|
||||||
[env]
|
|
||||||
platform = espressif32
|
|
||||||
framework = arduino
|
|
||||||
monitor_speed = 115200
|
|
||||||
lib_extra_dirs = ../libraries
|
|
||||||
lib_deps =
|
|
||||||
; 3.2.0 targets the Arduino 2.x core shipped by PlatformIO's stable ESP32
|
|
||||||
; platform. ConfigurableFirmata 3.4.0 switched its bundled PWM source to the
|
|
||||||
; Arduino 3.x LEDC API even when that unused source is compiled as a dependency.
|
|
||||||
https://github.com/firmata/ConfigurableFirmata.git#3.2.0
|
|
||||||
bblanchon/ArduinoJson@^7.4.2
|
|
||||||
madhephaestus/ESP32Servo@^3.0.8
|
|
||||||
|
|
||||||
; This is the generic ESP32-WROOM-32/DevKitC target used by boards carrying a
|
|
||||||
; CH340 or CP210x USB-to-UART bridge. Linux normally exposes it as ttyUSB*.
|
|
||||||
[env:esp32dev]
|
|
||||||
board = esp32dev
|
|
||||||
|
|
||||||
; Native USB boards use the same sketch and Firmata stream. These flags make the
|
|
||||||
; ESP32-S3's USB CDC serial port active at boot, normally appearing as ttyACM*.
|
|
||||||
[env:esp32-s3-devkitc-1]
|
|
||||||
board = esp32-s3-devkitc-1
|
|
||||||
build_flags =
|
|
||||||
-D ARDUINO_USB_MODE=1
|
|
||||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
#include <Arduino.h>
|
|
||||||
#include <ConfigurableFirmata.h>
|
|
||||||
#include <FirmataExt.h>
|
|
||||||
#include <RoverPeripheralFirmata.h>
|
|
||||||
|
|
||||||
FirmataExt firmataExtension;
|
|
||||||
RoverPeripheralFirmata peripheral("Rover GPIO");
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
// Every example pin is present on both the classic ESP32 DevKitC and the
|
|
||||||
// ESP32-S3 DevKitC. GPIO 19 and 20 are deliberately avoided because native-USB
|
|
||||||
// S3 boards use them for USB D- and D+.
|
|
||||||
constexpr uint8_t kSpecialActionPin = 21;
|
|
||||||
|
|
||||||
void runSpecialAction() {
|
|
||||||
// This intentionally represents arbitrary device behavior rather than a raw
|
|
||||||
// pin mapping. Replace it with a motor sequence, LED animation, actuator
|
|
||||||
// routine, or any other application-specific function the accessory needs.
|
|
||||||
digitalWrite(kSpecialActionPin, HIGH);
|
|
||||||
delay(80);
|
|
||||||
digitalWrite(kSpecialActionPin, LOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerBuiltInRoverControls() {
|
|
||||||
// These three roles replace physical GPIO backends while preserving the
|
|
||||||
// existing camera, headlight, and laser commands and HUD controls.
|
|
||||||
RoverCameraServoConfig cameraServo;
|
|
||||||
cameraServo.pin = 14;
|
|
||||||
cameraServo.minimumAngleDegrees = -15;
|
|
||||||
cameraServo.maximumAngleDegrees = 30;
|
|
||||||
cameraServo.homeAngleDegrees = 0;
|
|
||||||
cameraServo.nudgeDegrees = 2;
|
|
||||||
cameraServo.minimumPulseMicroseconds = 900;
|
|
||||||
cameraServo.maximumPulseMicroseconds = 2100;
|
|
||||||
cameraServo.allowRawPulse = false;
|
|
||||||
cameraServo.inverted = false;
|
|
||||||
peripheral.addRoverCameraServo(cameraServo);
|
|
||||||
|
|
||||||
RoverDigitalOutputConfig headlight;
|
|
||||||
headlight.pin = 18;
|
|
||||||
headlight.polarity = OutputPolarity::ActiveHigh;
|
|
||||||
headlight.initiallyOn = false;
|
|
||||||
peripheral.addRoverHeadlight(headlight);
|
|
||||||
|
|
||||||
RoverDigitalOutputConfig laser;
|
|
||||||
laser.pin = 16;
|
|
||||||
laser.polarity = OutputPolarity::ActiveHigh;
|
|
||||||
laser.initiallyOn = false;
|
|
||||||
peripheral.addRoverLaser(laser);
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerGenericControls() {
|
|
||||||
// Registration order is UI order. This servo slider is handled entirely by
|
|
||||||
// standard Firmata SET_PIN_MODE and EXTENDED_ANALOG messages from roverd.
|
|
||||||
SliderControlConfig servoPosition;
|
|
||||||
servoPosition.id = "servoPosition";
|
|
||||||
servoPosition.name = "Servo position";
|
|
||||||
servoPosition.minimum = 0;
|
|
||||||
servoPosition.maximum = 180;
|
|
||||||
|
|
||||||
FirmataServoOutput servoOutput;
|
|
||||||
servoOutput.pin = 13;
|
|
||||||
peripheral.addServoSlider(servoPosition, servoOutput);
|
|
||||||
|
|
||||||
// PWM brightness is another standard Firmata output. No sketch callback is
|
|
||||||
// involved when the driver moves this slider.
|
|
||||||
SliderControlConfig lightBrightness;
|
|
||||||
lightBrightness.id = "lightBrightness";
|
|
||||||
lightBrightness.name = "Light brightness";
|
|
||||||
lightBrightness.minimum = 0;
|
|
||||||
lightBrightness.maximum = 255;
|
|
||||||
|
|
||||||
FirmataPwmOutput lightOutput;
|
|
||||||
lightOutput.pin = 17;
|
|
||||||
peripheral.addPwmSlider(lightBrightness, lightOutput);
|
|
||||||
|
|
||||||
// A custom momentary control receives both press and release. This example
|
|
||||||
// runs a one-shot action only on press, but a motor could use both values to
|
|
||||||
// start while held and stop on release.
|
|
||||||
ButtonControlConfig specialAction;
|
|
||||||
specialAction.id = "specialAction";
|
|
||||||
specialAction.name = "Run special action";
|
|
||||||
specialAction.mode = ButtonMode::Momentary;
|
|
||||||
peripheral.addButton(specialAction, [](bool pressed) {
|
|
||||||
if (pressed) {
|
|
||||||
runSpecialAction();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
pinMode(kSpecialActionPin, OUTPUT);
|
|
||||||
digitalWrite(kSpecialActionPin, LOW);
|
|
||||||
|
|
||||||
registerBuiltInRoverControls();
|
|
||||||
registerGenericControls();
|
|
||||||
|
|
||||||
// Supplying Serial as a Stream keeps all protocol code identical between a
|
|
||||||
// CH340/CP210x UART bridge and native ESP32-S3 USB CDC. Only PlatformIO's S3
|
|
||||||
// build flags differ.
|
|
||||||
Serial.begin(115200);
|
|
||||||
Firmata.begin(Serial);
|
|
||||||
peripheral.begin(firmataExtension);
|
|
||||||
|
|
||||||
// A Firmata system reset establishes declared initial output states and also
|
|
||||||
// proves that all callbacks were installed before normal traffic begins.
|
|
||||||
Firmata.parse(SYSTEM_RESET);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// Processing one complete parser unit at a time prevents a long serial burst
|
|
||||||
// from starving application work while still draining ordinary USB traffic
|
|
||||||
// quickly on both supported transports.
|
|
||||||
while (Firmata.available()) {
|
|
||||||
Firmata.processInput();
|
|
||||||
if (!Firmata.isParsingMessage()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
peripheral.update();
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
# `/` Performance Investigation Summary
|
|
||||||
|
|
||||||
This is the high-level handoff for the default route at:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
https://rover.otter.land/
|
|
||||||
```
|
|
||||||
|
|
||||||
The detailed issue writeups live in `perf/issues/`.
|
|
||||||
|
|
||||||
## Main Finding
|
|
||||||
|
|
||||||
The `/` page is doing too much main-thread work for low-end devices. On the current powerful
|
|
||||||
machine it can appear smooth, but CPU throttling shows real risk for mobile and weaker
|
|
||||||
computers.
|
|
||||||
|
|
||||||
The biggest measured problem is repeated global input listener churn. The broader underlying
|
|
||||||
theme is that high-frequency live data and broad React context invalidation cause many parts
|
|
||||||
of the page to rerender or recommit DOM attributes.
|
|
||||||
|
|
||||||
## Key Measurements
|
|
||||||
|
|
||||||
### Clean `/` Profile, 8x Mobile CPU Throttle
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-15-51-398Z/report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Results:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
average frame interval: ~140ms
|
|
||||||
p95 frame interval: ~166.8ms
|
|
||||||
p99 frame interval: ~383.4ms
|
|
||||||
long tasks: 67
|
|
||||||
event listener delta: +13,481
|
|
||||||
JS heap delta: +22.76MB
|
|
||||||
ScriptDuration delta: ~35.85s
|
|
||||||
TaskDuration delta: ~52.88s
|
|
||||||
```
|
|
||||||
|
|
||||||
Top notable CPU self-time included:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
removeEventListener ~1.30s
|
|
||||||
addEventListener ~0.94s
|
|
||||||
```
|
|
||||||
|
|
||||||
### Listener Audit
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-03-31-625Z-listeners/listener-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Worst churn:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
keydown adds=8040 removes=8039
|
|
||||||
keyup adds=8040 removes=8039
|
|
||||||
blur adds=8040 removes=8039
|
|
||||||
gamepadconnected adds=8040 removes=8039
|
|
||||||
gamepaddisconnected adds=8040 removes=8039
|
|
||||||
```
|
|
||||||
|
|
||||||
### `/` Runtime Audit, 6x Mobile CPU Throttle, 30s
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-21-51-263Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Results:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
average frame interval: ~88ms
|
|
||||||
p95 frame interval: 150ms
|
|
||||||
p99 frame interval: 183ms
|
|
||||||
long tasks: 18
|
|
||||||
WebSocket messages: at least 1000 sampled
|
|
||||||
WebSocket bytes: ~798KB sampled
|
|
||||||
DOM mutation records: 41,062
|
|
||||||
DOM attribute records: 40,761
|
|
||||||
```
|
|
||||||
|
|
||||||
Top sampled WebSocket events:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
log:entry 526
|
|
||||||
sensorFrame 306
|
|
||||||
commandAck 145
|
|
||||||
session:sync 5
|
|
||||||
```
|
|
||||||
|
|
||||||
### `/` Runtime Audit With Mutation Targets, 6x Mobile CPU Throttle, 18s
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-22-44-714Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Results:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
average frame interval: ~116.6ms
|
|
||||||
p95 frame interval: ~166.7ms
|
|
||||||
p99 frame interval: ~316.6ms
|
|
||||||
long tasks: 16
|
|
||||||
DOM mutation records: 18,758
|
|
||||||
DOM attribute records: 18,490
|
|
||||||
```
|
|
||||||
|
|
||||||
Top WebSocket events:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
log:entry 850
|
|
||||||
sensorFrame 277
|
|
||||||
commandAck 200
|
|
||||||
session:sync 23
|
|
||||||
```
|
|
||||||
|
|
||||||
Top mutation targets:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
input.accent-emerald-400 name 5300
|
|
||||||
input.accent-emerald-400 type 2650
|
|
||||||
field-input name ~1300 each
|
|
||||||
div.h-full style 1238
|
|
||||||
path d 621
|
|
||||||
path fill 619
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ordered Work Plan
|
|
||||||
|
|
||||||
1. Fix `KeyboardInputManager` and `GamepadInputManager` listener churn.
|
|
||||||
2. Split/stabilize `ControlContext` so action consumers do not rerender on every state change.
|
|
||||||
3. Batch or gate `log:entry` socket updates.
|
|
||||||
4. Throttle visual telemetry rendering.
|
|
||||||
5. Memoize or lazy-mount `ReplaySourcesPanel`.
|
|
||||||
6. Split and memoize chat composer/nickname form.
|
|
||||||
7. Clean up or share timers.
|
|
||||||
|
|
||||||
## Reproduction Commands
|
|
||||||
|
|
||||||
Install Playwright dependency if missing:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install --prefix perf playwright@1.60.0
|
|
||||||
npx playwright install chromium
|
|
||||||
```
|
|
||||||
|
|
||||||
Clean CPU profile:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=8 VIEWPORT=390x844 MOBILE=1 node perf/live-cpu-profile.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
Listener audit:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
node perf/live-listener-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
Runtime audit:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
Runtime audit with shorter mutation target sample:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=18000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
The `perf/results/2026-06-11T03-11-39-596Z-route-sweep/` route sweep includes `/spectate`,
|
|
||||||
`/mini`, `/display`, and `/scanner`. Those are not the current priority. Use them only for
|
|
||||||
context.
|
|
||||||
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
# `/spectate` CPU Performance Summary
|
|
||||||
|
|
||||||
This is the `/spectate` companion to the `/` performance writeups. The testing target
|
|
||||||
was the deployed page:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
https://rover.otter.land/spectate
|
|
||||||
```
|
|
||||||
|
|
||||||
The most useful artifact for this pass is:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-12T04-55-16-888Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
That run used:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 \
|
|
||||||
node perf/live-root-runtime-audit.mjs https://rover.otter.land/spectate perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
## High-Level Result
|
|
||||||
|
|
||||||
`/spectate` is CPU-heavy for a different reason than `/`.
|
|
||||||
|
|
||||||
The `/` page's biggest confirmed issue is global input listener churn. `/spectate` does
|
|
||||||
not show that same dominant pattern. The fresh `/spectate` audit had 379 event listeners,
|
|
||||||
mostly normal media/image listeners, and no giant add/remove loop.
|
|
||||||
|
|
||||||
The `/spectate` hot path is instead a live-display fan-out problem:
|
|
||||||
|
|
||||||
- several rover media players are mounted at once
|
|
||||||
- each rover card renders live HUD telemetry and a top-down SVG map
|
|
||||||
- camera/snapshot frames create new blobs and `img.src` values
|
|
||||||
- the route subscribes to broad session/log/chat state while also rendering the whole
|
|
||||||
spectator layout
|
|
||||||
- the page maintains several WebRTC players at the same time
|
|
||||||
|
|
||||||
## Key Measurements
|
|
||||||
|
|
||||||
Fresh `/spectate` runtime audit, 30s, mobile viewport, 6x CPU throttle:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
TaskDuration: 28.48s over a 30.9s sample
|
|
||||||
ScriptDuration: 12.11s
|
|
||||||
Long tasks: 81
|
|
||||||
Worst long task: 375ms
|
|
||||||
Average frame gap: 113.9ms
|
|
||||||
p95 frame gap: 233.3ms
|
|
||||||
p99 frame gap: 300.0ms
|
|
||||||
Nodes: 7,059
|
|
||||||
Layout objects: 6,150
|
|
||||||
RTCPeerConnections: 6
|
|
||||||
LayoutCount: 243
|
|
||||||
RecalcStyleCount: 248
|
|
||||||
```
|
|
||||||
|
|
||||||
The 8x CPU-throttled profile from the earlier route comparison was worse:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Task delta: 49.13s over a 52.85s sample
|
|
||||||
Script delta: 19.58s
|
|
||||||
Layout delta: 516
|
|
||||||
Recalc delta: 522
|
|
||||||
Node delta: +3,815
|
|
||||||
```
|
|
||||||
|
|
||||||
That profile also put `Blob`, `decodeString`, and `setAttribute` in the CPU stack, which
|
|
||||||
matches the runtime evidence for camera blobs, socket payload parsing, and SVG/image
|
|
||||||
attribute churn.
|
|
||||||
|
|
||||||
## Socket And DOM Pressure
|
|
||||||
|
|
||||||
The fresh audit captured these live events during the 30s window:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
sensorFrame: 1,354 events, 2,531,090 bytes
|
|
||||||
log:entry: 634 events, 150,179 bytes
|
|
||||||
unlabeled binary: 126 events, 11,048,477 bytes
|
|
||||||
roomCamera:frame: 117 events
|
|
||||||
session:sync: 54 events, 914,000 bytes
|
|
||||||
commandAck: 68 events
|
|
||||||
roverHostStats: 17 events
|
|
||||||
roverSnapshot:frame: 7 events
|
|
||||||
```
|
|
||||||
|
|
||||||
The most important DOM mutations were:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
path d: 2,688 mutations
|
|
||||||
path fill: 2,682 mutations
|
|
||||||
img.h-full.w-full src: 117 mutations
|
|
||||||
span.h-2.w-2 class: 117 mutations
|
|
||||||
```
|
|
||||||
|
|
||||||
The `path` mutations point strongly at the per-rover `TopDownMap` SVG overlays. The
|
|
||||||
`img.src` and blink class mutations point at room camera snapshot rendering.
|
|
||||||
|
|
||||||
## Ordered Issue List
|
|
||||||
|
|
||||||
1. [Telemetry fan-out and always-on HUD maps](./spectate-issues/001-telemetry-fanout-hud-maps.md)
|
|
||||||
2. [Multi-rover WebRTC media fan-out](./spectate-issues/002-multi-rover-webrtc-media-fanout.md)
|
|
||||||
3. [Room camera and snapshot object URL churn](./spectate-issues/003-room-camera-snapshot-object-url-churn.md)
|
|
||||||
4. [Broad spectator session rerenders](./spectate-issues/004-broad-spectator-session-rerenders.md)
|
|
||||||
5. [Shared log/session stream pressure](./spectate-issues/005-shared-log-session-stream-pressure.md)
|
|
||||||
6. [Spectator role handshake retries](./spectate-issues/006-spectator-role-handshake-retries.md)
|
|
||||||
|
|
||||||
## Upstream Dependency Map
|
|
||||||
|
|
||||||
Fixing the `/` backlog first is a good plan.
|
|
||||||
|
|
||||||
Likely improved by `/` fixes:
|
|
||||||
|
|
||||||
- telemetry visual throttling
|
|
||||||
- log batching/gating
|
|
||||||
- chat/nickname churn
|
|
||||||
- session selector/store improvements
|
|
||||||
- generic timer cleanup
|
|
||||||
|
|
||||||
Not likely to be fixed by `/` alone:
|
|
||||||
|
|
||||||
- several simultaneous WHEP video/audio players
|
|
||||||
- per-rover spectator HUD maps
|
|
||||||
- room camera snapshot blob/object URL churn
|
|
||||||
- spectator role/subscribe handshake behavior
|
|
||||||
- mobile policy for how many live feeds should be active at once
|
|
||||||
|
|
||||||
## Suggested Re-Test
|
|
||||||
|
|
||||||
After each `/spectate` fix, run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 \
|
|
||||||
node perf/live-root-runtime-audit.mjs https://rover.otter.land/spectate perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
The most useful numbers to compare:
|
|
||||||
|
|
||||||
- `TaskDuration`
|
|
||||||
- `ScriptDuration`
|
|
||||||
- long task count and worst duration
|
|
||||||
- frame average/p95/p99
|
|
||||||
- `RTCPeerConnections`
|
|
||||||
- `mutationTargets` for `path d`, `path fill`, and `img src`
|
|
||||||
- `webSocketEvents` for `sensorFrame`, `log:entry`, `session:sync`, and binary frame volume
|
|
||||||
|
|
||||||
@@ -1,278 +0,0 @@
|
|||||||
# FIXED!
|
|
||||||
# Issue 001: Global Input Listener Churn
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The `/` page repeatedly removes and re-adds global keyboard and gamepad event listeners.
|
|
||||||
This is the clearest and most actionable CPU problem found on the control page.
|
|
||||||
|
|
||||||
On weaker CPUs, this turns into measurable main-thread cost. It also makes the input layer
|
|
||||||
fragile because every broad render/effect invalidation touches global browser listeners.
|
|
||||||
|
|
||||||
## Severity
|
|
||||||
|
|
||||||
High.
|
|
||||||
|
|
||||||
This should be fixed first. It is directly measured and likely amplifies other `/` issues.
|
|
||||||
|
|
||||||
## Affected Files
|
|
||||||
|
|
||||||
- `webui/src/controls/inputs/KeyboardInputManager.jsx`
|
|
||||||
- `webui/src/controls/inputs/GamepadInputManager.jsx`
|
|
||||||
- `webui/src/controls/inputs/gamepadHub.js`
|
|
||||||
- Related multiplier: `webui/src/controls/ControlContext.jsx`
|
|
||||||
|
|
||||||
## Evidence
|
|
||||||
|
|
||||||
### Listener Audit
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-03-31-625Z-listeners/listener-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
At the second snapshot, the audit saw:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
keydown adds=8040 removes=8039
|
|
||||||
keyup adds=8040 removes=8039
|
|
||||||
blur adds=8040 removes=8039
|
|
||||||
gamepadconnected adds=8040 removes=8039
|
|
||||||
gamepaddisconnected adds=8040 removes=8039
|
|
||||||
```
|
|
||||||
|
|
||||||
The stack mapped those to the live bundle locations corresponding to:
|
|
||||||
|
|
||||||
- `KeyboardInputManager.jsx` global `keydown`, `keyup`, `blur` effect
|
|
||||||
- `gamepadHub.js` global `gamepadconnected`, `gamepaddisconnected` listener registration
|
|
||||||
|
|
||||||
### Root Runtime Audit
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-21-51-263Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
In a 30s `/` run at mobile viewport with 6x CPU throttle:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
add:window:keydown 770
|
|
||||||
remove:window:keydown 769
|
|
||||||
add:window:keyup 770
|
|
||||||
remove:window:keyup 769
|
|
||||||
add:window:blur 770
|
|
||||||
remove:window:blur 769
|
|
||||||
add:window:gamepadconnected 770
|
|
||||||
remove:window:gamepadconnected 769
|
|
||||||
add:window:gamepaddisconnected 770
|
|
||||||
remove:window:gamepaddisconnected 769
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clean CPU Profile
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-15-51-398Z/report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Clean `/` profile, mobile viewport, 8x CPU throttle:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
average frame interval: ~140ms
|
|
||||||
p95 frame interval: ~166.8ms
|
|
||||||
p99 frame interval: ~383.4ms
|
|
||||||
long tasks: 67
|
|
||||||
event listener delta: +13,481
|
|
||||||
removeEventListener: ~1.30s self time
|
|
||||||
addEventListener: ~0.94s self time
|
|
||||||
```
|
|
||||||
|
|
||||||
## Likely Cause
|
|
||||||
|
|
||||||
`KeyboardInputManager` installs global listeners inside a `useEffect` with a very large
|
|
||||||
dependency list:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
useEffect(() => {
|
|
||||||
function handleKeyDown(event) { ... }
|
|
||||||
function handleKeyUp(event) { ... }
|
|
||||||
function handleBlur() { ... }
|
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown, { capture: true });
|
|
||||||
window.addEventListener('keyup', handleKeyUp, { capture: true });
|
|
||||||
window.addEventListener('blur', handleBlur);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('keydown', handleKeyDown, { capture: true });
|
|
||||||
window.removeEventListener('keyup', handleKeyUp, { capture: true });
|
|
||||||
window.removeEventListener('blur', handleBlur);
|
|
||||||
};
|
|
||||||
}, [
|
|
||||||
actionTokens,
|
|
||||||
blurChat,
|
|
||||||
driveFromKeys,
|
|
||||||
ensureServoLoop,
|
|
||||||
ensureSongLoop,
|
|
||||||
focusChat,
|
|
||||||
isChatFocused,
|
|
||||||
keymap.chatFocus,
|
|
||||||
...
|
|
||||||
dockAssist,
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
Many dependencies change when `ControlContext` or settings/session state changes. That
|
|
||||||
causes the effect to tear down and reinstall global listeners over and over.
|
|
||||||
|
|
||||||
`GamepadInputManager` has the same pattern in a subscription effect:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
useEffect(() => {
|
|
||||||
return subscribeGamepadHub((hubState) => { ... });
|
|
||||||
}, [
|
|
||||||
activeSignature,
|
|
||||||
ensureProfile,
|
|
||||||
gamepadSettings?.defaults?.profile,
|
|
||||||
gamepadSettings?.profiles,
|
|
||||||
handleButtonEdge,
|
|
||||||
handleCameraAxis,
|
|
||||||
registerInputState,
|
|
||||||
runMacro,
|
|
||||||
setAuxMotors,
|
|
||||||
setDriveVector,
|
|
||||||
setMode,
|
|
||||||
toggleHeadlight,
|
|
||||||
dockAssist,
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
When this effect resubscribes, `subscribeGamepadHub` may add/remove global device listeners
|
|
||||||
in `gamepadHub.js`.
|
|
||||||
|
|
||||||
## Fix Strategy
|
|
||||||
|
|
||||||
### KeyboardInputManager
|
|
||||||
|
|
||||||
Install global listeners once:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const latestRef = useRef(null);
|
|
||||||
|
|
||||||
latestRef.current = {
|
|
||||||
keymap,
|
|
||||||
actionTokens,
|
|
||||||
isChatFocused,
|
|
||||||
focusChat,
|
|
||||||
resetAll,
|
|
||||||
driveFromKeys,
|
|
||||||
ensureServoLoop,
|
|
||||||
ensureSongLoop,
|
|
||||||
...
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function handleKeyDown(event) {
|
|
||||||
const latest = latestRef.current;
|
|
||||||
if (!latest) return;
|
|
||||||
// Existing logic, reading from latest instead of closure variables.
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKeyUp(event) {
|
|
||||||
const latest = latestRef.current;
|
|
||||||
if (!latest) return;
|
|
||||||
// Existing logic.
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleBlur() {
|
|
||||||
latestRef.current?.resetAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown, { capture: true });
|
|
||||||
window.addEventListener('keyup', handleKeyUp, { capture: true });
|
|
||||||
window.addEventListener('blur', handleBlur);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('keydown', handleKeyDown, { capture: true });
|
|
||||||
window.removeEventListener('keyup', handleKeyUp, { capture: true });
|
|
||||||
window.removeEventListener('blur', handleBlur);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
```
|
|
||||||
|
|
||||||
Keep mutable input state in refs as it already mostly does:
|
|
||||||
|
|
||||||
- `activeTokensRef`
|
|
||||||
- `lastVectorRef`
|
|
||||||
- `lastAuxRef`
|
|
||||||
- `servoIntervalRef`
|
|
||||||
- `songIntervalRef`
|
|
||||||
- `hornActiveRef`
|
|
||||||
|
|
||||||
### GamepadInputManager
|
|
||||||
|
|
||||||
Use the same pattern:
|
|
||||||
|
|
||||||
- Keep latest settings/actions/state in a ref.
|
|
||||||
- Subscribe to `subscribeGamepadHub` once.
|
|
||||||
- The subscription callback reads latest values from the ref.
|
|
||||||
|
|
||||||
Do not resubscribe just because profile/settings/action references changed.
|
|
||||||
|
|
||||||
### gamepadHub.js
|
|
||||||
|
|
||||||
After `GamepadInputManager` is stable, confirm whether `gamepadHub.js` still needs changes.
|
|
||||||
It already tries to add global device listeners only when there are subscribers. The churn
|
|
||||||
is likely caused by subscriber churn, not necessarily a bug in the hub itself.
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Before fix:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
node perf/live-listener-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected current bad result:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
hundreds or thousands of add/remove pairs for:
|
|
||||||
keydown
|
|
||||||
keyup
|
|
||||||
blur
|
|
||||||
gamepadconnected
|
|
||||||
gamepaddisconnected
|
|
||||||
```
|
|
||||||
|
|
||||||
After fix:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
keydown add ~= 1
|
|
||||||
keyup add ~= 1
|
|
||||||
blur add ~= 1
|
|
||||||
gamepadconnected add ~= 1
|
|
||||||
gamepaddisconnected add ~= 1
|
|
||||||
removes only on page teardown
|
|
||||||
```
|
|
||||||
|
|
||||||
Also run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
CPU_THROTTLE=8 VIEWPORT=390x844 MOBILE=1 node perf/live-cpu-profile.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected improvements:
|
|
||||||
|
|
||||||
- `JSEventListeners` delta should stop climbing dramatically.
|
|
||||||
- `addEventListener` / `removeEventListener` should disappear from top CPU self-time.
|
|
||||||
- Average frame interval and long-task count should improve, though other issues will remain.
|
|
||||||
|
|
||||||
## Risks
|
|
||||||
|
|
||||||
- Keyboard control safety matters. Be careful to preserve `resetAll()` on blur and text-input
|
|
||||||
ignoring behavior.
|
|
||||||
- If using refs, avoid stale data bugs by updating the ref every render before browser events
|
|
||||||
can fire.
|
|
||||||
- Verify horn hold, mic push-to-talk, chat focus, drive macro, dock assist, camera tilt, song
|
|
||||||
controls, and Home Assistant shortcuts.
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
# FIXED!
|
|
||||||
# Issue 002: ControlContext Broad Invalidation
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
`ControlContext` exposes one large changing context value containing all control state,
|
|
||||||
pipeline objects, limiter state, and action callbacks. This likely causes many consumers
|
|
||||||
to re-render and effects to re-run when unrelated control state changes.
|
|
||||||
|
|
||||||
This is probably the multiplier behind the global input listener churn and some repeated
|
|
||||||
DOM commits on `/`.
|
|
||||||
|
|
||||||
## Severity
|
|
||||||
|
|
||||||
High, but fix after issue 001 unless doing both together.
|
|
||||||
|
|
||||||
Issue 001 is the symptom with the clearest measurement. Issue 002 is the architectural
|
|
||||||
cause that may also affect other parts of the page.
|
|
||||||
|
|
||||||
## Affected Files
|
|
||||||
|
|
||||||
- `webui/src/controls/ControlContext.jsx`
|
|
||||||
- `webui/src/controls/inputs/KeyboardInputManager.jsx`
|
|
||||||
- `webui/src/controls/inputs/GamepadInputManager.jsx`
|
|
||||||
- Many consumers of `useControlSystem()`
|
|
||||||
|
|
||||||
## Evidence
|
|
||||||
|
|
||||||
The `contextValue` in `ControlContext.jsx` is:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const contextValue = useMemo(
|
|
||||||
() => ({
|
|
||||||
state,
|
|
||||||
dispatch,
|
|
||||||
pipeline,
|
|
||||||
overcurrentLimiter,
|
|
||||||
actions: {
|
|
||||||
setMode,
|
|
||||||
setDriveVector,
|
|
||||||
setAuxMotors,
|
|
||||||
...
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
state,
|
|
||||||
pipeline,
|
|
||||||
overcurrentLimiter,
|
|
||||||
setMode,
|
|
||||||
setDriveVector,
|
|
||||||
...
|
|
||||||
],
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Any state change changes the whole context value. Any consumer using:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const { state, actions } = useControlSystem();
|
|
||||||
```
|
|
||||||
|
|
||||||
is subscribed to the broad value, even if it only needs one action or one small state field.
|
|
||||||
|
|
||||||
This connects to measured symptoms:
|
|
||||||
|
|
||||||
- Input listener effects rerun because their action/callback dependencies change.
|
|
||||||
- `/` runtime audit saw repeated attribute commits in UI that should not need to change every
|
|
||||||
telemetry/log tick.
|
|
||||||
- CPU profiles show large minified React frames (`C9`, `ad`, `Fl`, etc.) alongside listener
|
|
||||||
churn.
|
|
||||||
|
|
||||||
## Why This Matters
|
|
||||||
|
|
||||||
React context invalidation is coarse. When provider value identity changes, every consumer
|
|
||||||
below it can be scheduled to render. Even memoized children are not enough if they consume
|
|
||||||
the changing context directly.
|
|
||||||
|
|
||||||
This project has several high-frequency update sources:
|
|
||||||
|
|
||||||
- telemetry frames
|
|
||||||
- logs
|
|
||||||
- session syncs
|
|
||||||
- control state changes
|
|
||||||
- settings state
|
|
||||||
- timers
|
|
||||||
|
|
||||||
If `ControlContext` changes broadly, it makes it harder to isolate these updates.
|
|
||||||
|
|
||||||
## Likely Cause Pattern
|
|
||||||
|
|
||||||
Several action callbacks depend on `state` or `pipeline`, which changes their identity:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const setDriveVector = useCallback(..., [pipeline, recordControlIntent, state.manualDockAssist?.active]);
|
|
||||||
const setServoAngle = useCallback(..., [pipeline, recordControlIntent, state.manualDockAssist?.active]);
|
|
||||||
const runMacro = useCallback(..., [driveMacroBackoffEnabled, pipeline, ..., state.macros, ...]);
|
|
||||||
const startHorn = useCallback(..., [dispatch, normalizedHornSettings, pipeline, ..., state.horn?.active, ...]);
|
|
||||||
```
|
|
||||||
|
|
||||||
Those callbacks are then included in `contextValue`.
|
|
||||||
|
|
||||||
Consumers that include those callbacks in effects rerun when the callback identity changes.
|
|
||||||
|
|
||||||
## Fix Strategy
|
|
||||||
|
|
||||||
### Option A: Split State and Actions Contexts
|
|
||||||
|
|
||||||
Create separate contexts:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const ControlStateContext = createContext(null);
|
|
||||||
const ControlActionsContext = createContext(null);
|
|
||||||
```
|
|
||||||
|
|
||||||
Then:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<ControlStateContext.Provider value={stateValue}>
|
|
||||||
<ControlActionsContext.Provider value={actionsValue}>
|
|
||||||
{children}
|
|
||||||
</ControlActionsContext.Provider>
|
|
||||||
</ControlStateContext.Provider>
|
|
||||||
```
|
|
||||||
|
|
||||||
Action-only consumers should not rerender when state changes.
|
|
||||||
|
|
||||||
### Option B: Add Selectors
|
|
||||||
|
|
||||||
Create a small external-store style control store, similar to `SessionContext` or
|
|
||||||
`TelemetryContext`, so consumers can select only the state slice they need:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
useControlSelector((state) => state.camera.angle)
|
|
||||||
useControlActions()
|
|
||||||
```
|
|
||||||
|
|
||||||
This is more work but scales better.
|
|
||||||
|
|
||||||
### Option C: Stabilize Actions With Refs
|
|
||||||
|
|
||||||
Keep action function identities stable and read latest state/pipeline from refs:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const latestRef = useRef(null);
|
|
||||||
latestRef.current = { state, pipeline, overcurrentLimiter, ... };
|
|
||||||
|
|
||||||
const setDriveVector = useCallback((vector, meta = {}) => {
|
|
||||||
const { state, pipeline } = latestRef.current;
|
|
||||||
...
|
|
||||||
}, []);
|
|
||||||
```
|
|
||||||
|
|
||||||
This pairs well with issue 001 because stable actions make stable input effects easier.
|
|
||||||
|
|
||||||
## Recommended Path
|
|
||||||
|
|
||||||
For incremental work:
|
|
||||||
|
|
||||||
1. Fix issue 001 directly using refs in input managers.
|
|
||||||
2. Split `ControlActionsContext` from `ControlStateContext`.
|
|
||||||
3. Convert heavy/control-critical consumers first:
|
|
||||||
- `KeyboardInputManager`
|
|
||||||
- `GamepadInputManager`
|
|
||||||
- mobile controls
|
|
||||||
- right pane controls
|
|
||||||
4. Later add selector hooks for state-heavy consumers.
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
After issue 001 + partial issue 002:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
Look for:
|
|
||||||
|
|
||||||
- listener add/remove counts near 1
|
|
||||||
- fewer repeated input attribute mutations
|
|
||||||
- fewer long tasks
|
|
||||||
- lower `ScriptDuration` and `TaskDuration`
|
|
||||||
|
|
||||||
For development builds, React DevTools Profiler would be ideal to verify fewer renders. The
|
|
||||||
deployed production page does not currently publish source maps, so local profiling may be
|
|
||||||
more useful after implementing.
|
|
||||||
|
|
||||||
## Risks
|
|
||||||
|
|
||||||
- Ref-based stable actions can hide stale state bugs if refs are not updated reliably.
|
|
||||||
- Splitting context touches many files. Prefer a small staged migration.
|
|
||||||
- Control commands are safety-sensitive; verify stop/horn/mic/docking behavior carefully.
|
|
||||||
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
# FIXED!
|
|
||||||
# Issue 003: High-Volume Socket Log Stream
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The `/` page receives a very high volume of `log:entry` socket messages. These update the
|
|
||||||
global session store even when logs are not the primary visible task.
|
|
||||||
|
|
||||||
This is not the single biggest CPU stack item, but it is a major source of live update
|
|
||||||
pressure on low-end devices.
|
|
||||||
|
|
||||||
## Severity
|
|
||||||
|
|
||||||
Medium-high.
|
|
||||||
|
|
||||||
Fix after listener/context work, unless logs are known to be unusually noisy in production.
|
|
||||||
|
|
||||||
## Affected Files
|
|
||||||
|
|
||||||
- `webui/src/context/SessionContext.jsx`
|
|
||||||
- `webui/src/components/LogPanel/index.jsx`
|
|
||||||
- Server log emission paths, depending on where `log:entry` is emitted
|
|
||||||
|
|
||||||
## Evidence
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-22-44-714Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
In an 18s `/` audit at mobile viewport with 6x CPU throttle:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
log:entry count: 850
|
|
||||||
log:entry bytes: 185,008
|
|
||||||
```
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-21-51-263Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
In a 30s audit, among the most recent sampled 1000 WebSocket messages:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
log:entry count: 526
|
|
||||||
log:entry bytes: 111,535
|
|
||||||
```
|
|
||||||
|
|
||||||
In `SessionContext.jsx`, each log entry does:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
function handleLogEntry(entry) {
|
|
||||||
setState((prev) => ({ ...prev, logs: [...prev.logs.slice(-199), entry] }));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates a new session state object and a new logs array for every log message.
|
|
||||||
|
|
||||||
## Why This Matters
|
|
||||||
|
|
||||||
Even if `LogPanel` is not visible or not currently selected, `SessionContext` still processes
|
|
||||||
the update. Every session subscriber selector is evaluated on each `setState`.
|
|
||||||
|
|
||||||
At 850 log messages in 18s, this is roughly 47 log entries per second.
|
|
||||||
|
|
||||||
On a powerful desktop this is fine. On a weaker phone or low-end laptop, it becomes steady
|
|
||||||
background pressure.
|
|
||||||
|
|
||||||
## Likely Cause
|
|
||||||
|
|
||||||
The server appears to broadcast operational logs very frequently. The client stores the last
|
|
||||||
200 logs globally.
|
|
||||||
|
|
||||||
The `/` page includes `LogPanel` under settings and may also have other components depending
|
|
||||||
on session state.
|
|
||||||
|
|
||||||
## Fix Strategies
|
|
||||||
|
|
||||||
### Option A: Do Not Subscribe to Logs Unless Log UI Is Visible
|
|
||||||
|
|
||||||
Best conceptual fix:
|
|
||||||
|
|
||||||
- Add an explicit client event like `log:subscribe` / `log:unsubscribe`.
|
|
||||||
- Only subscribe when `LogPanel` is open or user is admin.
|
|
||||||
- For ordinary mobile control users, skip logs entirely.
|
|
||||||
|
|
||||||
### Option B: Batch Log Entries Client-Side
|
|
||||||
|
|
||||||
Keep receiving logs, but batch state updates:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const pendingLogsRef = useRef([]);
|
|
||||||
const flushTimerRef = useRef(null);
|
|
||||||
|
|
||||||
function handleLogEntry(entry) {
|
|
||||||
pendingLogsRef.current.push(entry);
|
|
||||||
if (flushTimerRef.current) return;
|
|
||||||
flushTimerRef.current = setTimeout(() => {
|
|
||||||
const batch = pendingLogsRef.current.splice(0);
|
|
||||||
flushTimerRef.current = null;
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
logs: [...prev.logs, ...batch].slice(-200),
|
|
||||||
}));
|
|
||||||
}, 250);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This changes 47 state updates/sec into about 4 updates/sec.
|
|
||||||
|
|
||||||
### Option C: Store Logs in a Separate External Store
|
|
||||||
|
|
||||||
Move logs out of main `SessionContext` so unrelated session consumers are not touched by
|
|
||||||
log spam.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
LogStoreProvider
|
|
||||||
useLogEntries()
|
|
||||||
```
|
|
||||||
|
|
||||||
This isolates log updates from user/roster/session state.
|
|
||||||
|
|
||||||
### Option D: Server-Side Sampling or Severity Filtering
|
|
||||||
|
|
||||||
Only send:
|
|
||||||
|
|
||||||
- warnings/errors by default
|
|
||||||
- full logs to admin/log panel subscribers
|
|
||||||
- sampled info/debug entries
|
|
||||||
|
|
||||||
## Recommended Path
|
|
||||||
|
|
||||||
Start with client batching. It is low-risk and easy to validate.
|
|
||||||
|
|
||||||
Then consider subscription gating if logs are not essential for normal users.
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
The socket `log:entry` count may remain high if only batching client-side, but UI state
|
|
||||||
updates should reduce. To measure batching directly, add temporary counters in
|
|
||||||
`SessionContext` or extend the audit to count React commits after local changes.
|
|
||||||
|
|
||||||
Expected user-visible perf improvements:
|
|
||||||
|
|
||||||
- lower `ScriptDuration`
|
|
||||||
- lower `TaskDuration`
|
|
||||||
- fewer repeated DOM commits if log updates were causing broad subscribers to render
|
|
||||||
|
|
||||||
## Risks
|
|
||||||
|
|
||||||
- Logs may be used for operator visibility during incidents.
|
|
||||||
- Batching can delay log display by 100-500ms. That should be acceptable for UI logs.
|
|
||||||
- Subscription gating requires server/client protocol changes.
|
|
||||||
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
# FIXED!
|
|
||||||
# Issue 004: Sensor Telemetry Render Frequency
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The `/` page receives frequent `sensorFrame` messages and renders multiple telemetry/HUD
|
|
||||||
consumers from those frames. On throttled mobile CPU, this shows up as repeated SVG path
|
|
||||||
attribute mutations and significant live DOM churn.
|
|
||||||
|
|
||||||
## Severity
|
|
||||||
|
|
||||||
Medium-high.
|
|
||||||
|
|
||||||
This is likely the biggest non-input-manager source of steady rendering work.
|
|
||||||
|
|
||||||
## Affected Files
|
|
||||||
|
|
||||||
- `webui/src/context/TelemetryContext.jsx`
|
|
||||||
- `webui/src/components/TopDownMap/TopDownMapContent.jsx`
|
|
||||||
- `webui/src/components/TopDownMap/visuals.jsx`
|
|
||||||
- `webui/src/components/DriverVideo/index.jsx`
|
|
||||||
- `webui/src/components/HudOverlays/*`
|
|
||||||
- `webui/src/components/TelemetryPanel/index.jsx`
|
|
||||||
- `webui/src/components/PiHostStatsCard/index.jsx`
|
|
||||||
|
|
||||||
## Evidence
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-22-44-714Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
In an 18s `/` audit at mobile viewport with 6x CPU throttle:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
sensorFrame count: 277
|
|
||||||
sensorFrame bytes: 520,169
|
|
||||||
```
|
|
||||||
|
|
||||||
That is about 15.4 sensor frames/sec.
|
|
||||||
|
|
||||||
The same audit saw SVG attribute mutations:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
path d mutations: 621
|
|
||||||
path fill mutations: 619
|
|
||||||
```
|
|
||||||
|
|
||||||
These map strongly to `TopDownMapContent.jsx` and `visuals.jsx`, where sensor values are
|
|
||||||
converted into SVG arcs, cones, wheels, brush visuals, and colors.
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-21-51-263Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
In a 30s audit:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
sensorFrame count among latest sampled 1000 messages: 306
|
|
||||||
sensorFrame bytes: 574,616
|
|
||||||
```
|
|
||||||
|
|
||||||
## Current Code Path
|
|
||||||
|
|
||||||
`TelemetryContext.jsx` updates every incoming frame:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
function handleSensorFrame({ roverId, sensors = {}, frame = {} }) {
|
|
||||||
if (!roverId) return;
|
|
||||||
const previous = framesRef.current[roverId] ?? {};
|
|
||||||
framesRef.current = {
|
|
||||||
...framesRef.current,
|
|
||||||
[roverId]: {
|
|
||||||
...previous,
|
|
||||||
roverId,
|
|
||||||
sensors,
|
|
||||||
raw: frame?.data || null,
|
|
||||||
receivedAt: Date.now(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
notifyRover(roverId);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Consumers subscribe by rover:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
useTelemetryFrame(roverId)
|
|
||||||
```
|
|
||||||
|
|
||||||
Every `notifyRover(roverId)` wakes all consumers for that rover.
|
|
||||||
|
|
||||||
## Why This Matters
|
|
||||||
|
|
||||||
Telemetry itself may need to remain high-frequency for control safety or debugging, but the
|
|
||||||
visual UI usually does not need to render at full sensor frequency.
|
|
||||||
|
|
||||||
The expensive part is not receiving the socket message. It is causing React/SVG/HUD updates
|
|
||||||
and DOM attribute changes at the same cadence.
|
|
||||||
|
|
||||||
## Fix Strategies
|
|
||||||
|
|
||||||
### Option A: Throttle Render Notifications
|
|
||||||
|
|
||||||
Keep the latest frame in `framesRef` immediately, but notify subscribers at a lower rate.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const pendingRoversRef = useRef(new Set());
|
|
||||||
const notifyScheduledRef = useRef(false);
|
|
||||||
|
|
||||||
function scheduleNotify(roverId) {
|
|
||||||
pendingRoversRef.current.add(roverId);
|
|
||||||
if (notifyScheduledRef.current) return;
|
|
||||||
notifyScheduledRef.current = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
notifyScheduledRef.current = false;
|
|
||||||
const rovers = [...pendingRoversRef.current];
|
|
||||||
pendingRoversRef.current.clear();
|
|
||||||
rovers.forEach((id) => notifyRover(id));
|
|
||||||
}, 100); // 10Hz visual updates
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then `handleSensorFrame` writes latest data immediately but calls `scheduleNotify(roverId)`.
|
|
||||||
|
|
||||||
### Option B: Separate Raw Telemetry From Visual Telemetry
|
|
||||||
|
|
||||||
Expose:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
useTelemetryFrameRaw(roverId) // high-frequency, only for critical logic
|
|
||||||
useTelemetryFrameVisual(roverId) // throttled for UI rendering
|
|
||||||
```
|
|
||||||
|
|
||||||
Use throttled visual telemetry in:
|
|
||||||
|
|
||||||
- TopDownMap
|
|
||||||
- HUD overlays
|
|
||||||
- telemetry panels
|
|
||||||
- battery bars
|
|
||||||
- host stats display
|
|
||||||
|
|
||||||
Keep raw telemetry for safety mechanisms if needed.
|
|
||||||
|
|
||||||
### Option C: Memoize/Split Sensor Consumers by Field
|
|
||||||
|
|
||||||
Do not re-render the entire map or all overlays when only one sensor field changed.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- Battery components subscribe only to battery fields.
|
|
||||||
- Light bump bars subscribe only to light bump fields.
|
|
||||||
- Overcurrent overlay subscribes only to overcurrent fields.
|
|
||||||
|
|
||||||
This is more architectural but can reduce unnecessary work.
|
|
||||||
|
|
||||||
### Option D: Render TopDownMap Less Often
|
|
||||||
|
|
||||||
At the component level:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const visualSensors = useThrottledValue(sensors, 100);
|
|
||||||
```
|
|
||||||
|
|
||||||
This is easier than changing `TelemetryContext`, but less central.
|
|
||||||
|
|
||||||
## Recommended Path
|
|
||||||
|
|
||||||
Start with a throttled visual notification path in `TelemetryContext`. It gives the largest
|
|
||||||
blast-radius reduction without changing every consumer immediately.
|
|
||||||
|
|
||||||
A reasonable target:
|
|
||||||
|
|
||||||
- Desktop: 10-15Hz visual updates
|
|
||||||
- Mobile: 5-10Hz visual updates
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=18000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected improvements:
|
|
||||||
|
|
||||||
- `path d` and `path fill` mutation counts should drop.
|
|
||||||
- `LayoutCount` and `RecalcStyleCount` may drop.
|
|
||||||
- `ScriptDuration` should drop.
|
|
||||||
- Frame p95 should improve.
|
|
||||||
|
|
||||||
Socket `sensorFrame` count may remain the same if only rendering is throttled.
|
|
||||||
|
|
||||||
## Risks
|
|
||||||
|
|
||||||
- Do not throttle logic that protects the rover or sends control commands unless you are sure
|
|
||||||
it is only display logic.
|
|
||||||
- Operators may expect smooth sensor visuals, but 10Hz is usually enough for dashboards.
|
|
||||||
- Make sure low battery/overcurrent warnings still feel responsive.
|
|
||||||
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
# Issue 005: ReplaySourcesPanel Repeated DOM Commits
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
`ReplaySourcesPanel` appears to re-commit many input/checkbox attributes repeatedly on `/`,
|
|
||||||
especially in mobile portrait where the panel is always mounted near the top of the page.
|
|
||||||
|
|
||||||
This may be a component-local issue, or it may be caused by broader context/session updates.
|
|
||||||
Tackle it after issues 001 and 002 unless profiling still shows it as large.
|
|
||||||
|
|
||||||
## Severity
|
|
||||||
|
|
||||||
Medium.
|
|
||||||
|
|
||||||
It is visible in mutation probes, but likely partly downstream of broader invalidation.
|
|
||||||
|
|
||||||
## Affected Files
|
|
||||||
|
|
||||||
- `webui/src/components/ReplaySourcesPanel/index.jsx`
|
|
||||||
- `webui/src/App.jsx`
|
|
||||||
- `webui/src/context/SessionContext.jsx`
|
|
||||||
- `webui/src/settings/SettingsProvider.jsx`
|
|
||||||
|
|
||||||
## Evidence
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-22-44-714Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
In an 18s `/` audit at mobile viewport with 6x CPU throttle, top mutation targets included:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
input.accent-emerald-400 name: 5300
|
|
||||||
input.accent-emerald-400 type: 2650
|
|
||||||
input#replay-sources-mobile-portrait-title name: 666
|
|
||||||
input#replay-sources-mobile-portrait-title type: 333
|
|
||||||
```
|
|
||||||
|
|
||||||
The `accent-emerald-400` class maps to `ReplaySourcesPanel` checkboxes:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={includeSidebar}
|
|
||||||
...
|
|
||||||
className="accent-emerald-400"
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
and source item checkboxes:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={selected.includes(item.key)}
|
|
||||||
onChange={() => onToggle(item.key)}
|
|
||||||
className="accent-emerald-400"
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
The mobile portrait route mounts it here:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<ReplaySourcesPanel panelId="replay-sources-mobile-portrait" />
|
|
||||||
```
|
|
||||||
|
|
||||||
in `App.jsx`.
|
|
||||||
|
|
||||||
## Why This Matters
|
|
||||||
|
|
||||||
Repeated DOM attribute commits are a symptom of repeated React commits. Even if the attribute
|
|
||||||
values do not change semantically, React still touches DOM attributes when the subtree
|
|
||||||
rerenders/commits.
|
|
||||||
|
|
||||||
The panel is not central to driving, so it should not be doing meaningful work during normal
|
|
||||||
control-page idle.
|
|
||||||
|
|
||||||
## Likely Causes
|
|
||||||
|
|
||||||
The component subscribes to several session slices:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const replaySources = useSessionSelector((state) => state.session?.replaySources ?? []);
|
|
||||||
const mode = useSessionSelector((state) => state.session?.mode || null);
|
|
||||||
const assignmentRoverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
|
|
||||||
const roster = useSessionSelector((state) => state.session?.roster ?? []);
|
|
||||||
const replayState = useSessionSelector((state) => state.session?.replay || null);
|
|
||||||
const latestReplay = useSessionSelector((state) => state.latestReplay);
|
|
||||||
```
|
|
||||||
|
|
||||||
Potential problems:
|
|
||||||
|
|
||||||
- `session:sync` may provide new array/object identities even when contents are unchanged.
|
|
||||||
- `normalizeSources(replaySources || [])` runs every render and produces new objects.
|
|
||||||
- `GroupList` is not memoized.
|
|
||||||
- `selected.includes(item.key)` is recomputed for each item on each render.
|
|
||||||
- `remainingMs` interval updates every 250ms when cooldown is active.
|
|
||||||
- Parent/mobile layout re-renders can also re-render this panel.
|
|
||||||
|
|
||||||
## Fix Strategies
|
|
||||||
|
|
||||||
### Option A: Memoize Normalized Sources
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const sources = useMemo(
|
|
||||||
() => normalizeSources(replaySources || []),
|
|
||||||
[replaySources],
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
This is already not memoized in the current code.
|
|
||||||
|
|
||||||
### Option B: Use Shallow Equality Selectors
|
|
||||||
|
|
||||||
For arrays like `replaySources` and `roster`, use a selector/equality function that avoids
|
|
||||||
rerendering when content did not change.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const replaySources = useSessionSelector(
|
|
||||||
(state) => state.session?.replaySources ?? [],
|
|
||||||
shallowReplaySourcesEqual,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option C: Memoize GroupList
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const GroupList = React.memo(function GroupList(...) { ... });
|
|
||||||
```
|
|
||||||
|
|
||||||
Also memoize:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const selectedSet = useMemo(() => new Set(selected), [selected]);
|
|
||||||
```
|
|
||||||
|
|
||||||
Then use `selectedSet.has(item.key)`.
|
|
||||||
|
|
||||||
### Option D: Do Not Mount Replay Panel By Default on Mobile
|
|
||||||
|
|
||||||
If replay is not a core mobile driving workflow, consider putting it behind a tab/accordion
|
|
||||||
or lazy mounting it only when expanded.
|
|
||||||
|
|
||||||
This is a product decision.
|
|
||||||
|
|
||||||
### Option E: Stop Passing Unstable Callbacks
|
|
||||||
|
|
||||||
Memoize callbacks:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const toggleKey = useCallback((key) => { ... }, []);
|
|
||||||
const handleReplay = useCallback(async () => { ... }, [...]);
|
|
||||||
```
|
|
||||||
|
|
||||||
This helps if child components are memoized.
|
|
||||||
|
|
||||||
## Recommended Path
|
|
||||||
|
|
||||||
1. Fix issues 001 and 002 first.
|
|
||||||
2. Rerun root runtime audit.
|
|
||||||
3. If `ReplaySourcesPanel` still dominates mutation targets:
|
|
||||||
- memoize `sources`
|
|
||||||
- memoize `GroupList`
|
|
||||||
- add shallow equality selectors
|
|
||||||
- consider lazy mounting on mobile
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=18000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected improvement:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
input.accent-emerald-400 name/type mutations should drop sharply
|
|
||||||
replay title input name/type mutations should drop
|
|
||||||
```
|
|
||||||
|
|
||||||
## Risks
|
|
||||||
|
|
||||||
- Replay source selections and saved panel settings must still update correctly.
|
|
||||||
- If lazy mounting, preserve persisted title/sidebar settings.
|
|
||||||
- Do not break replay cooldown display.
|
|
||||||
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
# Issue 006: Chat and Nickname Composer Churn
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The mobile `/` page defaults to the chat tab, and the chat/nickname composer shows repeated
|
|
||||||
DOM attribute commits in runtime audits. This is not the top CPU issue, but it is part of
|
|
||||||
steady idle churn.
|
|
||||||
|
|
||||||
## Severity
|
|
||||||
|
|
||||||
Medium-low to medium.
|
|
||||||
|
|
||||||
Fix after the listener/context/log/telemetry work unless chat remains high in mutation
|
|
||||||
reports.
|
|
||||||
|
|
||||||
## Affected Files
|
|
||||||
|
|
||||||
- `webui/src/components/ChatPanel/index.jsx`
|
|
||||||
- `webui/src/components/NicknameForm/index.jsx`
|
|
||||||
- `webui/src/context/ChatContext.jsx`
|
|
||||||
- `webui/src/App.jsx`
|
|
||||||
|
|
||||||
## Evidence
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-22-44-714Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
In an 18s `/` audit at mobile viewport with 6x CPU throttle:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
input.field-input.chat-composer-input name: 666
|
|
||||||
input.field-input.flex-1 name: 1328
|
|
||||||
input.field-input.w-full name: 1332
|
|
||||||
input.accent-cyan-500 name: 1324
|
|
||||||
input.accent-cyan-500 type: 662
|
|
||||||
```
|
|
||||||
|
|
||||||
These map to:
|
|
||||||
|
|
||||||
- chat composer input
|
|
||||||
- nickname form input
|
|
||||||
- TTS/speak checkbox
|
|
||||||
- related chat form controls
|
|
||||||
|
|
||||||
The chat panel is mounted by default in mobile feature tabs:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const [activeTab, setActiveTab] = useState('chat');
|
|
||||||
...
|
|
||||||
<TabPanel id="chat">
|
|
||||||
<ChatPanel nicknameLayout="stacked" />
|
|
||||||
...
|
|
||||||
</TabPanel>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Why This Matters
|
|
||||||
|
|
||||||
The chat UI is useful, but it should not re-commit its composer inputs hundreds of times
|
|
||||||
while the user is simply watching/driving.
|
|
||||||
|
|
||||||
Also, text inputs and focus-related logic interact with keyboard driving. Excess churn here
|
|
||||||
can have indirect effects on input handling.
|
|
||||||
|
|
||||||
## Likely Causes
|
|
||||||
|
|
||||||
`ChatPanel` consumes:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const role = useSessionSelector((state) => state.session?.role || null);
|
|
||||||
const currentRoverId = useSessionSelector((state) => state.session?.assignment?.roverId || null);
|
|
||||||
const roster = useSessionSelector((state) => state.session?.roster ?? []);
|
|
||||||
const { messages, typing, sendMessage, ... } = useChat();
|
|
||||||
const { value: ttsSettings, save: saveTtsSettings } = useSettingsNamespace('tts', ...);
|
|
||||||
```
|
|
||||||
|
|
||||||
It also has effects that sync local state from settings:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
useEffect(() => {
|
|
||||||
const nextEngine = ...
|
|
||||||
if (engine !== nextEngine) setEngine(nextEngine);
|
|
||||||
...
|
|
||||||
}, [engine, googlePitch, googleSpeed, pitch, ttsSettings..., voice]);
|
|
||||||
```
|
|
||||||
|
|
||||||
Potential problems:
|
|
||||||
|
|
||||||
- broad session/chat context updates rerender the whole panel
|
|
||||||
- message list and composer are in the same component
|
|
||||||
- typing events cause panel updates
|
|
||||||
- nickname form may be rerendering with the whole chat panel
|
|
||||||
- TTS settings sync effect has many dependencies
|
|
||||||
|
|
||||||
## Fix Strategies
|
|
||||||
|
|
||||||
### Option A: Split ChatPanel Into Memoized Subcomponents
|
|
||||||
|
|
||||||
Separate:
|
|
||||||
|
|
||||||
- `ChatMessageList`
|
|
||||||
- `ChatComposer`
|
|
||||||
- `TtsControls`
|
|
||||||
- `NicknameForm`
|
|
||||||
|
|
||||||
Only `ChatMessageList` should rerender when messages change.
|
|
||||||
Only `ChatComposer` should rerender when draft/sending/focus changes.
|
|
||||||
|
|
||||||
### Option B: Memoize Composer
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const ChatComposer = React.memo(function ChatComposer(props) { ... });
|
|
||||||
```
|
|
||||||
|
|
||||||
Pass stable callbacks where possible.
|
|
||||||
|
|
||||||
### Option C: Debounce Typing Updates
|
|
||||||
|
|
||||||
`setTypingActive(Boolean(next.trim()))` fires on every keystroke. That is fine while typing,
|
|
||||||
but should not be involved during idle. If it causes socket chatter, debounce it.
|
|
||||||
|
|
||||||
### Option D: Stabilize NicknameForm
|
|
||||||
|
|
||||||
If `NicknameForm` consumes settings/session state broadly, make it use precise selectors and
|
|
||||||
memoize it.
|
|
||||||
|
|
||||||
### Option E: Avoid Re-Syncing Local TTS State Too Often
|
|
||||||
|
|
||||||
The settings sync effect can be simplified or guarded so it only runs when the settings
|
|
||||||
object actually changes, not on every local state update.
|
|
||||||
|
|
||||||
## Recommended Path
|
|
||||||
|
|
||||||
1. Fix issues 001-004 first.
|
|
||||||
2. Rerun mutation audit.
|
|
||||||
3. If chat/nickname inputs remain high:
|
|
||||||
- split `ChatPanel`
|
|
||||||
- memoize composer
|
|
||||||
- stabilize `NicknameForm`
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=18000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected improvements:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
chat-composer-input mutations should drop
|
|
||||||
NicknameForm input mutations should drop
|
|
||||||
accent-cyan-500 checkbox mutations should drop
|
|
||||||
```
|
|
||||||
|
|
||||||
Also manually verify:
|
|
||||||
|
|
||||||
- typing
|
|
||||||
- sending chat
|
|
||||||
- TTS options
|
|
||||||
- nickname edit/save
|
|
||||||
- Enter-to-focus/send chat shortcut
|
|
||||||
|
|
||||||
## Risks
|
|
||||||
|
|
||||||
- Chat focus behavior is tied to keyboard controls.
|
|
||||||
- Splitting components can accidentally break `registerInputRef`.
|
|
||||||
- TTS settings must still persist correctly.
|
|
||||||
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
# FIXED!
|
|
||||||
# Issue 007: Timers and Polling Cleanup
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The `/` page creates multiple intervals and timeouts. They are not the dominant problem,
|
|
||||||
but they contribute to steady background work and can wake components even when hidden.
|
|
||||||
|
|
||||||
## Severity
|
|
||||||
|
|
||||||
Low to medium.
|
|
||||||
|
|
||||||
Address after the larger structural issues unless a specific timer is found to be hot.
|
|
||||||
|
|
||||||
## Affected Files
|
|
||||||
|
|
||||||
Known or likely timer users:
|
|
||||||
|
|
||||||
- `webui/src/components/AlertFeed/index.jsx`
|
|
||||||
- `webui/src/components/ReplaySourcesPanel/index.jsx`
|
|
||||||
- `webui/src/components/HudOverlays/TurnsOverlay/index.jsx`
|
|
||||||
- `webui/src/components/RoverQueuesPanel/index.jsx`
|
|
||||||
- `webui/src/components/ModeGateOverlay/index.jsx`
|
|
||||||
- `webui/src/components/SocketConnectionPill/index.jsx`
|
|
||||||
- `webui/src/controls/ControlContext.jsx` horn heat interval
|
|
||||||
- `webui/src/controls/overcurrentLimiter.js`
|
|
||||||
|
|
||||||
## Evidence
|
|
||||||
|
|
||||||
Artifact:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
perf/results/2026-06-11T03-22-44-714Z-root-runtime/root-runtime-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
In an 18s `/` audit at mobile viewport with 6x CPU throttle:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
intervals created: 22
|
|
||||||
timeouts created: 11
|
|
||||||
interval fires: 148
|
|
||||||
timeout fires: 6
|
|
||||||
```
|
|
||||||
|
|
||||||
Most common timer registrations:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
interval:200 7
|
|
||||||
timeout:12000 4
|
|
||||||
interval:60000 4
|
|
||||||
interval:250 4
|
|
||||||
interval:3000 4
|
|
||||||
interval:1000 2
|
|
||||||
```
|
|
||||||
|
|
||||||
In a 30s audit:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
interval fires: 593
|
|
||||||
```
|
|
||||||
|
|
||||||
## Why This Matters
|
|
||||||
|
|
||||||
Timers wake the main thread. On a fast desktop they disappear into the noise; on a throttled
|
|
||||||
mobile CPU, timers can combine with socket traffic and React work to create visible stalls.
|
|
||||||
|
|
||||||
The timer issue is not that any one interval is terrible. It is that many UI elements keep
|
|
||||||
time independently.
|
|
||||||
|
|
||||||
## Likely Sources
|
|
||||||
|
|
||||||
### ReplaySourcesPanel
|
|
||||||
|
|
||||||
Cooldown display:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const interval = setInterval(update, 250);
|
|
||||||
```
|
|
||||||
|
|
||||||
Only needed while cooldown is active and panel is visible.
|
|
||||||
|
|
||||||
### AlertFeed
|
|
||||||
|
|
||||||
Likely uses a frequent interval for alert lifetimes. This should only run while alerts exist.
|
|
||||||
|
|
||||||
### TurnsOverlay / RoverQueuesPanel
|
|
||||||
|
|
||||||
Countdown displays often use `setInterval(() => setNow(Date.now()), 250 or 1000)`.
|
|
||||||
|
|
||||||
### ControlContext Horn Heat
|
|
||||||
|
|
||||||
Horn heat interval at 100ms only runs when horn is active/cooling:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const tickMs = 100;
|
|
||||||
const interval = setInterval(...)
|
|
||||||
```
|
|
||||||
|
|
||||||
This is probably okay, but verify it only runs when needed.
|
|
||||||
|
|
||||||
## Fix Strategies
|
|
||||||
|
|
||||||
### Option A: Visibility-Gate Timers
|
|
||||||
|
|
||||||
Only run panel-specific timers when the panel is visible/open.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
if (!isVisible || !needsCountdown) return;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option B: Coarse Intervals
|
|
||||||
|
|
||||||
Use 1000ms for human-readable countdowns unless sub-second precision matters.
|
|
||||||
|
|
||||||
Replay cooldowns and queue countdowns probably do not need 250ms.
|
|
||||||
|
|
||||||
### Option C: Shared Clock Store
|
|
||||||
|
|
||||||
Instead of each component creating its own interval, create one shared clock:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
useClock(1000)
|
|
||||||
useClock(250)
|
|
||||||
```
|
|
||||||
|
|
||||||
Then multiple components can share a single interval.
|
|
||||||
|
|
||||||
### Option D: CSS Animations for Pure Visual Expiry
|
|
||||||
|
|
||||||
For alert progress bars, use CSS animation if the UI does not need React state each tick.
|
|
||||||
|
|
||||||
## Recommended Path
|
|
||||||
|
|
||||||
After major fixes, rerun the runtime audit. If intervals still show high:
|
|
||||||
|
|
||||||
1. Increase replay cooldown interval from 250ms to 1000ms.
|
|
||||||
2. Gate timers by tab visibility.
|
|
||||||
3. Create a shared clock hook for countdown UI.
|
|
||||||
4. Convert alert lifetime visuals to CSS where possible.
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected:
|
|
||||||
|
|
||||||
- lower `intervals`
|
|
||||||
- lower `intervalFires`
|
|
||||||
- no visible regression in countdowns/alerts
|
|
||||||
|
|
||||||
## Risks
|
|
||||||
|
|
||||||
- Timers used for control safety should not be slowed without careful review.
|
|
||||||
- Alert expiration should still happen reliably.
|
|
||||||
- Queue/turn countdowns can update less often, but should not become misleading.
|
|
||||||
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# `/` CPU Performance Issue Index
|
|
||||||
|
|
||||||
This folder breaks the live-site `/` CPU investigation into separate, work-ready issues.
|
|
||||||
Each issue can be handled in its own chat or branch without needing to re-run the whole
|
|
||||||
investigation first.
|
|
||||||
|
|
||||||
The testing target was the deployed page:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
https://rover.otter.land/
|
|
||||||
```
|
|
||||||
|
|
||||||
The important local perf tools are:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
node perf/live-cpu-profile.mjs https://rover.otter.land/ perf/results
|
|
||||||
node perf/live-listener-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
|
|
||||||
```
|
|
||||||
|
|
||||||
The strongest `/` artifacts from this investigation are:
|
|
||||||
|
|
||||||
- `perf/results/2026-06-11T03-15-51-398Z/report.json`
|
|
||||||
Clean `/` profile, mobile viewport, 8x CPU throttle.
|
|
||||||
- `perf/results/2026-06-11T03-03-31-625Z-listeners/listener-report.json`
|
|
||||||
Listener add/remove stack audit.
|
|
||||||
- `perf/results/2026-06-11T03-21-51-263Z-root-runtime/root-runtime-report.json`
|
|
||||||
`/` runtime audit, mobile viewport, 6x CPU throttle, 30s sample.
|
|
||||||
- `perf/results/2026-06-11T03-22-44-714Z-root-runtime/root-runtime-report.json`
|
|
||||||
`/` runtime audit, mobile viewport, 6x CPU throttle, 18s sample with mutation targets.
|
|
||||||
|
|
||||||
## Ranked Issues
|
|
||||||
|
|
||||||
1. [Global input listener churn](./001-global-input-listener-churn.md)
|
|
||||||
2. [ControlContext broad invalidation](./002-control-context-broad-invalidation.md)
|
|
||||||
3. [High-volume socket log stream](./003-high-volume-log-stream.md)
|
|
||||||
4. [Sensor telemetry render frequency](./004-sensor-telemetry-render-frequency.md)
|
|
||||||
5. [ReplaySourcesPanel repeated DOM commits](./005-replay-sources-panel-churn.md)
|
|
||||||
6. [Chat and nickname composer churn](./006-chat-and-nickname-churn.md)
|
|
||||||
7. [Timers and polling cleanup](./007-timers-and-polling.md)
|
|
||||||
|
|
||||||
## Quick Read
|
|
||||||
|
|
||||||
The highest-confidence first fix is issue 001. It is directly measured, large, and
|
|
||||||
structurally fixable: install keyboard/gamepad global listeners once and have them read
|
|
||||||
latest state/actions from refs.
|
|
||||||
|
|
||||||
Issue 002 is the likely multiplier behind issue 001 and several UI churn symptoms. The
|
|
||||||
current `ControlContext` value changes broadly, and many callbacks depend on `state` or
|
|
||||||
`pipeline`; that causes consumers/effects to refresh even when the visible behavior did
|
|
||||||
not materially change.
|
|
||||||
|
|
||||||
Issues 003 and 004 are independent live-data pressure: the `/` page receives a lot of
|
|
||||||
socket traffic, especially `log:entry` and `sensorFrame`, and those streams feed UI work.
|
|
||||||
|
|
||||||
Issue 005 and issue 006 are component-level symptoms visible in DOM mutation probes. They
|
|
||||||
should probably be tackled after the context/listener work, because broad invalidation may
|
|
||||||
be causing some of their repeated commits.
|
|
||||||
|
|
||||||
## Important Scope Note
|
|
||||||
|
|
||||||
There are also `/spectate` results in `perf/results` because the investigation briefly
|
|
||||||
compared routes. The user later clarified that `/spectate` is not the current priority.
|
|
||||||
These issue writeups intentionally focus on `/` only.
|
|
||||||
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
import fs from 'node:fs/promises';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { chromium } from 'playwright';
|
|
||||||
|
|
||||||
const url = process.argv[2] || 'https://rover.otter.land';
|
|
||||||
const outDir = process.argv[3] || path.resolve('perf/results');
|
|
||||||
const cpuThrottle = Number(process.env.CPU_THROTTLE || 1);
|
|
||||||
const viewport = (process.env.VIEWPORT || '1440x950').split('x').map((part) => Number(part));
|
|
||||||
const isMobile = process.env.MOBILE === '1';
|
|
||||||
const runId = new Date().toISOString().replace(/[:.]/g, '-');
|
|
||||||
const runDir = path.join(outDir, runId);
|
|
||||||
|
|
||||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
function summarizeCpuProfile(profile) {
|
|
||||||
const nodes = new Map(profile.nodes.map((node) => [node.id, node]));
|
|
||||||
const totals = new Map();
|
|
||||||
const samples = profile.samples || [];
|
|
||||||
const deltas = profile.timeDeltas || [];
|
|
||||||
let totalUs = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < samples.length; i += 1) {
|
|
||||||
const delta = deltas[i] || 0;
|
|
||||||
totalUs += delta;
|
|
||||||
const node = nodes.get(samples[i]);
|
|
||||||
if (!node) continue;
|
|
||||||
const frame = node.callFrame || {};
|
|
||||||
const key = `${frame.url || '(anonymous)'}::${frame.functionName || '(anonymous)'}`;
|
|
||||||
const prev = totals.get(key) || {
|
|
||||||
url: frame.url || '',
|
|
||||||
functionName: frame.functionName || '(anonymous)',
|
|
||||||
lineNumber: frame.lineNumber,
|
|
||||||
columnNumber: frame.columnNumber,
|
|
||||||
selfMs: 0,
|
|
||||||
samples: 0,
|
|
||||||
};
|
|
||||||
prev.selfMs += delta / 1000;
|
|
||||||
prev.samples += 1;
|
|
||||||
totals.set(key, prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalProfileMs: totalUs / 1000,
|
|
||||||
sampleCount: samples.length,
|
|
||||||
topSelfTime: [...totals.values()]
|
|
||||||
.sort((a, b) => b.selfMs - a.selfMs)
|
|
||||||
.slice(0, 30),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function interestingMetrics(metrics) {
|
|
||||||
const wanted = [
|
|
||||||
'Timestamp',
|
|
||||||
'Documents',
|
|
||||||
'Frames',
|
|
||||||
'JSEventListeners',
|
|
||||||
'Nodes',
|
|
||||||
'LayoutCount',
|
|
||||||
'RecalcStyleCount',
|
|
||||||
'LayoutDuration',
|
|
||||||
'RecalcStyleDuration',
|
|
||||||
'ScriptDuration',
|
|
||||||
'TaskDuration',
|
|
||||||
'JSHeapUsedSize',
|
|
||||||
'JSHeapTotalSize',
|
|
||||||
];
|
|
||||||
return Object.fromEntries(
|
|
||||||
metrics.metrics
|
|
||||||
.filter((metric) => wanted.includes(metric.name))
|
|
||||||
.map((metric) => [metric.name, metric.value]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function performInteractions(page) {
|
|
||||||
await page.mouse.wheel(0, 1400);
|
|
||||||
await sleep(800);
|
|
||||||
await page.mouse.wheel(0, -900);
|
|
||||||
await sleep(800);
|
|
||||||
await page.keyboard.press('Tab');
|
|
||||||
await sleep(300);
|
|
||||||
await page.keyboard.press('Tab');
|
|
||||||
await sleep(300);
|
|
||||||
|
|
||||||
const buttons = await page.locator('button:visible').elementHandles();
|
|
||||||
for (const button of buttons.slice(0, 3)) {
|
|
||||||
const box = await button.boundingBox();
|
|
||||||
if (!box || box.width < 8 || box.height < 8) continue;
|
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
||||||
await sleep(150);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
await fs.mkdir(runDir, { recursive: true });
|
|
||||||
const browser = await chromium.launch({
|
|
||||||
headless: true,
|
|
||||||
args: [
|
|
||||||
'--autoplay-policy=no-user-gesture-required',
|
|
||||||
'--disable-background-timer-throttling',
|
|
||||||
'--disable-renderer-backgrounding',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const context = await browser.newContext({
|
|
||||||
viewport: { width: viewport[0] || 1440, height: viewport[1] || 950 },
|
|
||||||
deviceScaleFactor: 1,
|
|
||||||
isMobile,
|
|
||||||
hasTouch: isMobile,
|
|
||||||
ignoreHTTPSErrors: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.addInitScript(() => {
|
|
||||||
window.__perfProbe = {
|
|
||||||
longTasks: [],
|
|
||||||
frames: [],
|
|
||||||
intervalDrifts: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
new PerformanceObserver((list) => {
|
|
||||||
for (const entry of list.getEntries()) {
|
|
||||||
window.__perfProbe.longTasks.push({
|
|
||||||
name: entry.name,
|
|
||||||
startTime: entry.startTime,
|
|
||||||
duration: entry.duration,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).observe({ type: 'longtask', buffered: true });
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
let lastFrame = performance.now();
|
|
||||||
const frameLoop = (now) => {
|
|
||||||
window.__perfProbe.frames.push(now - lastFrame);
|
|
||||||
lastFrame = now;
|
|
||||||
requestAnimationFrame(frameLoop);
|
|
||||||
};
|
|
||||||
requestAnimationFrame(frameLoop);
|
|
||||||
|
|
||||||
let lastTick = performance.now();
|
|
||||||
setInterval(() => {
|
|
||||||
const now = performance.now();
|
|
||||||
window.__perfProbe.intervalDrifts.push(now - lastTick - 250);
|
|
||||||
lastTick = now;
|
|
||||||
}, 250);
|
|
||||||
});
|
|
||||||
|
|
||||||
const page = await context.newPage();
|
|
||||||
const consoleMessages = [];
|
|
||||||
const failedRequests = [];
|
|
||||||
const responses = [];
|
|
||||||
|
|
||||||
page.on('console', (message) => {
|
|
||||||
consoleMessages.push({
|
|
||||||
type: message.type(),
|
|
||||||
text: message.text().slice(0, 1000),
|
|
||||||
location: message.location(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
page.on('requestfailed', (request) => {
|
|
||||||
failedRequests.push({
|
|
||||||
url: request.url(),
|
|
||||||
method: request.method(),
|
|
||||||
failure: request.failure()?.errorText,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
page.on('response', (response) => {
|
|
||||||
const status = response.status();
|
|
||||||
if (status >= 400) responses.push({ url: response.url(), status });
|
|
||||||
});
|
|
||||||
|
|
||||||
const cdp = await context.newCDPSession(page);
|
|
||||||
await cdp.send('Performance.enable');
|
|
||||||
await cdp.send('Profiler.enable');
|
|
||||||
if (cpuThrottle > 1) {
|
|
||||||
await cdp.send('Emulation.setCPUThrottlingRate', { rate: cpuThrottle });
|
|
||||||
}
|
|
||||||
|
|
||||||
const start = Date.now();
|
|
||||||
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
||||||
await page.waitForLoadState('load', { timeout: 60000 }).catch(() => {});
|
|
||||||
await sleep(5000);
|
|
||||||
const afterLoadMetrics = interestingMetrics(await cdp.send('Performance.getMetrics'));
|
|
||||||
await page.screenshot({ path: path.join(runDir, 'loaded.png'), fullPage: true });
|
|
||||||
|
|
||||||
await cdp.send('Profiler.start');
|
|
||||||
await sleep(15000);
|
|
||||||
await performInteractions(page);
|
|
||||||
await sleep(15000);
|
|
||||||
const { profile } = await cdp.send('Profiler.stop');
|
|
||||||
const endMetrics = interestingMetrics(await cdp.send('Performance.getMetrics'));
|
|
||||||
|
|
||||||
const perfProbe = await page.evaluate(() => window.__perfProbe);
|
|
||||||
await page.screenshot({ path: path.join(runDir, 'after-profile.png'), fullPage: true });
|
|
||||||
|
|
||||||
const cpuSummary = summarizeCpuProfile(profile);
|
|
||||||
const frameDeltas = perfProbe.frames.slice(5);
|
|
||||||
const longTasks = perfProbe.longTasks.sort((a, b) => b.duration - a.duration);
|
|
||||||
const intervalDrifts = perfProbe.intervalDrifts;
|
|
||||||
const percentile = (values, p) => {
|
|
||||||
if (!values.length) return 0;
|
|
||||||
const sorted = [...values].sort((a, b) => a - b);
|
|
||||||
return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * p))];
|
|
||||||
};
|
|
||||||
|
|
||||||
const report = {
|
|
||||||
url,
|
|
||||||
runDir,
|
|
||||||
elapsedMs: Date.now() - start,
|
|
||||||
userAgent: await page.evaluate(() => navigator.userAgent),
|
|
||||||
title: await page.title(),
|
|
||||||
finalUrl: page.url(),
|
|
||||||
environment: {
|
|
||||||
cpuThrottle,
|
|
||||||
viewport: { width: viewport[0] || 1440, height: viewport[1] || 950 },
|
|
||||||
isMobile,
|
|
||||||
},
|
|
||||||
metrics: {
|
|
||||||
afterLoad: afterLoadMetrics,
|
|
||||||
end: endMetrics,
|
|
||||||
delta: Object.fromEntries(
|
|
||||||
Object.keys(endMetrics).map((key) => [key, endMetrics[key] - (afterLoadMetrics[key] || 0)]),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
cpuSummary,
|
|
||||||
responsiveness: {
|
|
||||||
longTaskCount: longTasks.length,
|
|
||||||
topLongTasks: longTasks.slice(0, 20),
|
|
||||||
frameCount: frameDeltas.length,
|
|
||||||
averageFrameMs: frameDeltas.reduce((sum, value) => sum + value, 0) / frameDeltas.length,
|
|
||||||
p95FrameMs: percentile(frameDeltas, 0.95),
|
|
||||||
p99FrameMs: percentile(frameDeltas, 0.99),
|
|
||||||
intervalDriftCount: intervalDrifts.length,
|
|
||||||
averageIntervalDriftMs:
|
|
||||||
intervalDrifts.reduce((sum, value) => sum + value, 0) / intervalDrifts.length,
|
|
||||||
p95IntervalDriftMs: percentile(intervalDrifts, 0.95),
|
|
||||||
p99IntervalDriftMs: percentile(intervalDrifts, 0.99),
|
|
||||||
},
|
|
||||||
consoleMessages,
|
|
||||||
failedRequests,
|
|
||||||
errorResponses: responses,
|
|
||||||
};
|
|
||||||
|
|
||||||
await fs.writeFile(path.join(runDir, 'cpu-profile.cpuprofile'), JSON.stringify(profile));
|
|
||||||
await fs.writeFile(path.join(runDir, 'report.json'), JSON.stringify(report, null, 2));
|
|
||||||
console.log(JSON.stringify(report, null, 2));
|
|
||||||
await browser.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
run().catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
process.exitCode = 1;
|
|
||||||
});
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
import fs from 'node:fs/promises';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { chromium } from 'playwright';
|
|
||||||
|
|
||||||
const url = process.argv[2] || 'https://rover.otter.land';
|
|
||||||
const outDir = process.argv[3] || path.resolve('perf/results');
|
|
||||||
const runId = `${new Date().toISOString().replace(/[:.]/g, '-')}-listeners`;
|
|
||||||
const runDir = path.join(outDir, runId);
|
|
||||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
await fs.mkdir(runDir, { recursive: true });
|
|
||||||
const browser = await chromium.launch({ headless: true });
|
|
||||||
const context = await browser.newContext({
|
|
||||||
viewport: { width: 1440, height: 950 },
|
|
||||||
ignoreHTTPSErrors: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.addInitScript(() => {
|
|
||||||
const originalAdd = EventTarget.prototype.addEventListener;
|
|
||||||
const originalRemove = EventTarget.prototype.removeEventListener;
|
|
||||||
const listenerIds = new WeakMap();
|
|
||||||
let nextListenerId = 1;
|
|
||||||
const rows = new Map();
|
|
||||||
|
|
||||||
function listenerId(listener) {
|
|
||||||
if ((typeof listener !== 'function' && typeof listener !== 'object') || listener === null) {
|
|
||||||
return String(listener);
|
|
||||||
}
|
|
||||||
let id = listenerIds.get(listener);
|
|
||||||
if (!id) {
|
|
||||||
id = nextListenerId++;
|
|
||||||
listenerIds.set(listener, id);
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function targetName(target) {
|
|
||||||
if (target === window) return 'window';
|
|
||||||
if (target === document) return 'document';
|
|
||||||
if (target?.nodeType === Node.DOCUMENT_NODE) return 'document';
|
|
||||||
if (target?.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
const tag = target.tagName?.toLowerCase() || 'element';
|
|
||||||
const id = target.id ? `#${target.id}` : '';
|
|
||||||
const className = String(target.className || '').trim().split(/\s+/).slice(0, 3).join('.');
|
|
||||||
return `${tag}${id}${className ? `.${className}` : ''}`;
|
|
||||||
}
|
|
||||||
if (target?.constructor?.name) return target.constructor.name;
|
|
||||||
return String(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
function shortStack() {
|
|
||||||
return String(new Error().stack || '')
|
|
||||||
.split('\n')
|
|
||||||
.slice(3, 9)
|
|
||||||
.map((line) => line.trim().replace(location.origin, ''))
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function note(kind, target, type, listener) {
|
|
||||||
const stack = shortStack();
|
|
||||||
const key = `${targetName(target)}::${type}::${stack}`;
|
|
||||||
const row = rows.get(key) || {
|
|
||||||
target: targetName(target),
|
|
||||||
type: String(type),
|
|
||||||
stack,
|
|
||||||
adds: 0,
|
|
||||||
removes: 0,
|
|
||||||
activeApprox: 0,
|
|
||||||
firstSeen: performance.now(),
|
|
||||||
lastSeen: 0,
|
|
||||||
};
|
|
||||||
row[kind] += 1;
|
|
||||||
row.activeApprox += kind === 'adds' ? 1 : -1;
|
|
||||||
row.lastSeen = performance.now();
|
|
||||||
row.exampleListener = String(listener).slice(0, 180);
|
|
||||||
rows.set(key, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
EventTarget.prototype.addEventListener = function patchedAdd(type, listener, options) {
|
|
||||||
note('adds', this, type, listener);
|
|
||||||
return originalAdd.call(this, type, listener, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
EventTarget.prototype.removeEventListener = function patchedRemove(type, listener, options) {
|
|
||||||
note('removes', this, type, listener);
|
|
||||||
return originalRemove.call(this, type, listener, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.__listenerAudit = {
|
|
||||||
snapshot() {
|
|
||||||
return [...rows.values()]
|
|
||||||
.sort((a, b) => Math.abs(b.activeApprox) - Math.abs(a.activeApprox) || b.adds - a.adds)
|
|
||||||
.slice(0, 100);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const page = await context.newPage();
|
|
||||||
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
||||||
await page.waitForLoadState('load', { timeout: 60000 }).catch(() => {});
|
|
||||||
await sleep(10000);
|
|
||||||
const first = await page.evaluate(() => window.__listenerAudit.snapshot());
|
|
||||||
await page.mouse.wheel(0, 1500);
|
|
||||||
await sleep(1000);
|
|
||||||
await page.mouse.wheel(0, -900);
|
|
||||||
await sleep(20000);
|
|
||||||
const second = await page.evaluate(() => window.__listenerAudit.snapshot());
|
|
||||||
|
|
||||||
const report = { url, finalUrl: page.url(), first, second };
|
|
||||||
await fs.writeFile(path.join(runDir, 'listener-report.json'), JSON.stringify(report, null, 2));
|
|
||||||
await page.screenshot({ path: path.join(runDir, 'listener-audit.png'), fullPage: true });
|
|
||||||
console.log(JSON.stringify(report, null, 2));
|
|
||||||
await browser.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
run().catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
process.exitCode = 1;
|
|
||||||
});
|
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
import fs from 'node:fs/promises';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { chromium } from 'playwright';
|
|
||||||
|
|
||||||
const url = process.argv[2] || 'https://rover.otter.land/';
|
|
||||||
const outDir = process.argv[3] || path.resolve('perf/results');
|
|
||||||
const cpuThrottle = Number(process.env.CPU_THROTTLE || 6);
|
|
||||||
const viewport = (process.env.VIEWPORT || '390x844').split('x').map((part) => Number(part));
|
|
||||||
const isMobile = process.env.MOBILE !== '0';
|
|
||||||
const sampleMs = Number(process.env.SAMPLE_MS || 45000);
|
|
||||||
const runId = `${new Date().toISOString().replace(/[:.]/g, '-')}-root-runtime`;
|
|
||||||
const runDir = path.join(outDir, runId);
|
|
||||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
function percentile(values, p) {
|
|
||||||
if (!values.length) return 0;
|
|
||||||
const sorted = [...values].sort((a, b) => a - b);
|
|
||||||
return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * p))];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
await fs.mkdir(runDir, { recursive: true });
|
|
||||||
const browser = await chromium.launch({ headless: true });
|
|
||||||
const context = await browser.newContext({
|
|
||||||
viewport: { width: viewport[0] || 390, height: viewport[1] || 844 },
|
|
||||||
deviceScaleFactor: isMobile ? 3 : 1,
|
|
||||||
isMobile,
|
|
||||||
hasTouch: isMobile,
|
|
||||||
ignoreHTTPSErrors: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.addInitScript(() => {
|
|
||||||
const data = {
|
|
||||||
startedAt: performance.now(),
|
|
||||||
listeners: new Map(),
|
|
||||||
timers: new Map(),
|
|
||||||
timeouts: 0,
|
|
||||||
intervals: 0,
|
|
||||||
intervalFires: 0,
|
|
||||||
timeoutFires: 0,
|
|
||||||
webSocketMessages: [],
|
|
||||||
webSocketCounts: new Map(),
|
|
||||||
webSocketBytesByEvent: new Map(),
|
|
||||||
mutations: [],
|
|
||||||
mutationTargets: new Map(),
|
|
||||||
frames: [],
|
|
||||||
longTasks: [],
|
|
||||||
consoleErrors: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
function localPercentile(values, p) {
|
|
||||||
if (!values.length) return 0;
|
|
||||||
const sorted = [...values].sort((a, b) => a - b);
|
|
||||||
return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * p))];
|
|
||||||
}
|
|
||||||
|
|
||||||
function targetName(target) {
|
|
||||||
if (target === window) return 'window';
|
|
||||||
if (target === document) return 'document';
|
|
||||||
if (target?.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
const tag = target.tagName?.toLowerCase() || 'element';
|
|
||||||
const id = target.id ? `#${target.id}` : '';
|
|
||||||
const classes = String(target.className || '').split(/\s+/).filter(Boolean).slice(0, 2).join('.');
|
|
||||||
return `${tag}${id}${classes ? `.${classes}` : ''}`;
|
|
||||||
}
|
|
||||||
return target?.constructor?.name || String(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
function listenerKey(target, type, kind) {
|
|
||||||
return `${kind}:${targetName(target)}:${String(type)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const originalAdd = EventTarget.prototype.addEventListener;
|
|
||||||
const originalRemove = EventTarget.prototype.removeEventListener;
|
|
||||||
EventTarget.prototype.addEventListener = function add(type, listener, options) {
|
|
||||||
const key = listenerKey(this, type, 'add');
|
|
||||||
data.listeners.set(key, (data.listeners.get(key) || 0) + 1);
|
|
||||||
return originalAdd.call(this, type, listener, options);
|
|
||||||
};
|
|
||||||
EventTarget.prototype.removeEventListener = function remove(type, listener, options) {
|
|
||||||
const key = listenerKey(this, type, 'remove');
|
|
||||||
data.listeners.set(key, (data.listeners.get(key) || 0) + 1);
|
|
||||||
return originalRemove.call(this, type, listener, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
const originalSetInterval = window.setInterval;
|
|
||||||
const originalSetTimeout = window.setTimeout;
|
|
||||||
window.setInterval = function patchedSetInterval(fn, delay, ...args) {
|
|
||||||
data.intervals += 1;
|
|
||||||
const key = `interval:${delay}`;
|
|
||||||
data.timers.set(key, (data.timers.get(key) || 0) + 1);
|
|
||||||
return originalSetInterval.call(this, (...innerArgs) => {
|
|
||||||
data.intervalFires += 1;
|
|
||||||
return typeof fn === 'function' ? fn(...innerArgs) : eval(fn);
|
|
||||||
}, delay, ...args);
|
|
||||||
};
|
|
||||||
window.setTimeout = function patchedSetTimeout(fn, delay, ...args) {
|
|
||||||
data.timeouts += 1;
|
|
||||||
const key = `timeout:${delay}`;
|
|
||||||
data.timers.set(key, (data.timers.get(key) || 0) + 1);
|
|
||||||
return originalSetTimeout.call(this, (...innerArgs) => {
|
|
||||||
data.timeoutFires += 1;
|
|
||||||
return typeof fn === 'function' ? fn(...innerArgs) : eval(fn);
|
|
||||||
}, delay, ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
const OriginalWebSocket = window.WebSocket;
|
|
||||||
if (OriginalWebSocket) {
|
|
||||||
window.WebSocket = function PatchedWebSocket(...args) {
|
|
||||||
const ws = new OriginalWebSocket(...args);
|
|
||||||
ws.addEventListener('message', (event) => {
|
|
||||||
const raw = event.data;
|
|
||||||
const text = typeof raw === 'string' ? raw : '';
|
|
||||||
const match = text.slice(0, 120).match(/\["([^"\]]+)/);
|
|
||||||
const eventName = match?.[1] || text.slice(0, 12);
|
|
||||||
data.webSocketCounts.set(eventName, (data.webSocketCounts.get(eventName) || 0) + 1);
|
|
||||||
data.webSocketBytesByEvent.set(
|
|
||||||
eventName,
|
|
||||||
(data.webSocketBytesByEvent.get(eventName) || 0) + (typeof raw === 'string' ? raw.length : raw?.byteLength || raw?.size || 0),
|
|
||||||
);
|
|
||||||
data.webSocketMessages.push({
|
|
||||||
at: Math.round(performance.now()),
|
|
||||||
size: typeof raw === 'string' ? raw.length : raw?.byteLength || raw?.size || 0,
|
|
||||||
prefix: text.slice(0, 80),
|
|
||||||
});
|
|
||||||
if (data.webSocketMessages.length > 1000) data.webSocketMessages.shift();
|
|
||||||
});
|
|
||||||
return ws;
|
|
||||||
};
|
|
||||||
window.WebSocket.prototype = OriginalWebSocket.prototype;
|
|
||||||
Object.assign(window.WebSocket, OriginalWebSocket);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
new PerformanceObserver((list) => {
|
|
||||||
for (const entry of list.getEntries()) {
|
|
||||||
data.longTasks.push({ startTime: entry.startTime, duration: entry.duration, name: entry.name });
|
|
||||||
}
|
|
||||||
}).observe({ type: 'longtask', buffered: true });
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
let lastFrame = performance.now();
|
|
||||||
requestAnimationFrame(function frame(now) {
|
|
||||||
data.frames.push(now - lastFrame);
|
|
||||||
lastFrame = now;
|
|
||||||
requestAnimationFrame(frame);
|
|
||||||
});
|
|
||||||
|
|
||||||
new MutationObserver((records) => {
|
|
||||||
let added = 0;
|
|
||||||
let removed = 0;
|
|
||||||
let attrs = 0;
|
|
||||||
let text = 0;
|
|
||||||
for (const record of records) {
|
|
||||||
added += record.addedNodes?.length || 0;
|
|
||||||
removed += record.removedNodes?.length || 0;
|
|
||||||
if (record.type === 'attributes') attrs += 1;
|
|
||||||
if (record.type === 'characterData') text += 1;
|
|
||||||
if (record.type === 'attributes' || record.type === 'characterData') {
|
|
||||||
const key = `${targetName(record.target)}:${record.type}:${record.attributeName || ''}`;
|
|
||||||
data.mutationTargets.set(key, (data.mutationTargets.get(key) || 0) + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.mutations.push({ at: Math.round(performance.now()), records: records.length, added, removed, attrs, text });
|
|
||||||
if (data.mutations.length > 1000) data.mutations.shift();
|
|
||||||
}).observe(document, {
|
|
||||||
subtree: true,
|
|
||||||
childList: true,
|
|
||||||
attributes: true,
|
|
||||||
characterData: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const originalConsoleError = console.error;
|
|
||||||
console.error = (...args) => {
|
|
||||||
data.consoleErrors.push(args.map((arg) => String(arg)).join(' ').slice(0, 500));
|
|
||||||
return originalConsoleError.apply(console, args);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.__rootAudit = {
|
|
||||||
snapshot() {
|
|
||||||
const frames = data.frames.slice(5);
|
|
||||||
const messages = data.webSocketMessages;
|
|
||||||
const socketBuckets = new Map(
|
|
||||||
[...data.webSocketCounts.entries()].map(([event, count]) => [
|
|
||||||
event,
|
|
||||||
{ event, count, bytes: data.webSocketBytesByEvent.get(event) || 0 },
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
elapsedMs: performance.now() - data.startedAt,
|
|
||||||
listeners: [...data.listeners.entries()].map(([key, count]) => ({ key, count })).sort((a, b) => b.count - a.count).slice(0, 40),
|
|
||||||
timers: [...data.timers.entries()].map(([key, count]) => ({ key, count })).sort((a, b) => b.count - a.count).slice(0, 40),
|
|
||||||
timeouts: data.timeouts,
|
|
||||||
intervals: data.intervals,
|
|
||||||
timeoutFires: data.timeoutFires,
|
|
||||||
intervalFires: data.intervalFires,
|
|
||||||
webSocketMessageCount: messages.length,
|
|
||||||
webSocketBytes: messages.reduce((sum, message) => sum + message.size, 0),
|
|
||||||
webSocketEvents: [...socketBuckets.values()].sort((a, b) => b.count - a.count),
|
|
||||||
mutationsTotal: data.mutations.reduce((acc, row) => ({
|
|
||||||
records: acc.records + row.records,
|
|
||||||
added: acc.added + row.added,
|
|
||||||
removed: acc.removed + row.removed,
|
|
||||||
attrs: acc.attrs + row.attrs,
|
|
||||||
text: acc.text + row.text,
|
|
||||||
}), { records: 0, added: 0, removed: 0, attrs: 0, text: 0 }),
|
|
||||||
mutationTargets: [...data.mutationTargets.entries()]
|
|
||||||
.map(([key, count]) => ({ key, count }))
|
|
||||||
.sort((a, b) => b.count - a.count)
|
|
||||||
.slice(0, 50),
|
|
||||||
mutationBursts: [...data.mutations].sort((a, b) => b.records - a.records).slice(0, 20),
|
|
||||||
frames: {
|
|
||||||
count: frames.length,
|
|
||||||
average: frames.reduce((sum, value) => sum + value, 0) / frames.length,
|
|
||||||
p95: localPercentile(frames, 0.95),
|
|
||||||
p99: localPercentile(frames, 0.99),
|
|
||||||
},
|
|
||||||
longTasks: {
|
|
||||||
count: data.longTasks.length,
|
|
||||||
top: [...data.longTasks].sort((a, b) => b.duration - a.duration).slice(0, 20),
|
|
||||||
},
|
|
||||||
consoleErrors: data.consoleErrors,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const page = await context.newPage();
|
|
||||||
const cdp = await context.newCDPSession(page);
|
|
||||||
await cdp.send('Performance.enable');
|
|
||||||
if (cpuThrottle > 1) await cdp.send('Emulation.setCPUThrottlingRate', { rate: cpuThrottle });
|
|
||||||
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 90000 });
|
|
||||||
await page.waitForLoadState('load', { timeout: 90000 }).catch(() => {});
|
|
||||||
await sleep(sampleMs);
|
|
||||||
const metrics = await cdp.send('Performance.getMetrics');
|
|
||||||
const snapshot = await page.evaluate(() => window.__rootAudit.snapshot());
|
|
||||||
const report = {
|
|
||||||
url,
|
|
||||||
runDir,
|
|
||||||
environment: { cpuThrottle, viewport: { width: viewport[0] || 390, height: viewport[1] || 844 }, isMobile, sampleMs },
|
|
||||||
metrics: Object.fromEntries(metrics.metrics.map((metric) => [metric.name, metric.value])),
|
|
||||||
snapshot,
|
|
||||||
};
|
|
||||||
await page.screenshot({ path: path.join(runDir, 'root-runtime.png'), fullPage: true });
|
|
||||||
await fs.writeFile(path.join(runDir, 'root-runtime-report.json'), JSON.stringify(report, null, 2));
|
|
||||||
console.log(JSON.stringify(report, null, 2));
|
|
||||||
await browser.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
process.exitCode = 1;
|
|
||||||
});
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
import fs from 'node:fs/promises';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { chromium } from 'playwright';
|
|
||||||
|
|
||||||
const baseUrl = process.argv[2] || 'https://rover.otter.land';
|
|
||||||
const outDir = process.argv[3] || path.resolve('perf/results');
|
|
||||||
const cpuThrottle = Number(process.env.CPU_THROTTLE || 6);
|
|
||||||
const viewport = (process.env.VIEWPORT || '390x844').split('x').map((part) => Number(part));
|
|
||||||
const isMobile = process.env.MOBILE !== '0';
|
|
||||||
const sampleMs = Number(process.env.SAMPLE_MS || 22000);
|
|
||||||
const routes = (process.env.ROUTES || '/,/spectate,/mini,/display,/scanner')
|
|
||||||
.split(',')
|
|
||||||
.map((route) => route.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
const runId = `${new Date().toISOString().replace(/[:.]/g, '-')}-route-sweep`;
|
|
||||||
const runDir = path.join(outDir, runId);
|
|
||||||
|
|
||||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
function percentile(values, p) {
|
|
||||||
if (!values.length) return 0;
|
|
||||||
const sorted = [...values].sort((a, b) => a - b);
|
|
||||||
return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * p))];
|
|
||||||
}
|
|
||||||
|
|
||||||
function interestingMetrics(metrics) {
|
|
||||||
const wanted = [
|
|
||||||
'Documents',
|
|
||||||
'JSEventListeners',
|
|
||||||
'Nodes',
|
|
||||||
'LayoutCount',
|
|
||||||
'RecalcStyleCount',
|
|
||||||
'LayoutDuration',
|
|
||||||
'RecalcStyleDuration',
|
|
||||||
'ScriptDuration',
|
|
||||||
'TaskDuration',
|
|
||||||
'JSHeapUsedSize',
|
|
||||||
'JSHeapTotalSize',
|
|
||||||
];
|
|
||||||
return Object.fromEntries(
|
|
||||||
metrics.metrics
|
|
||||||
.filter((metric) => wanted.includes(metric.name))
|
|
||||||
.map((metric) => [metric.name, metric.value]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function summarizeCpuProfile(profile) {
|
|
||||||
const nodes = new Map(profile.nodes.map((node) => [node.id, node]));
|
|
||||||
const totals = new Map();
|
|
||||||
let totalUs = 0;
|
|
||||||
for (let i = 0; i < (profile.samples || []).length; i += 1) {
|
|
||||||
const delta = profile.timeDeltas?.[i] || 0;
|
|
||||||
totalUs += delta;
|
|
||||||
const frame = nodes.get(profile.samples[i])?.callFrame || {};
|
|
||||||
const key = `${frame.url || '(browser)'}::${frame.functionName || '(anonymous)'}:${frame.lineNumber}:${frame.columnNumber}`;
|
|
||||||
const row = totals.get(key) || {
|
|
||||||
url: frame.url || '',
|
|
||||||
functionName: frame.functionName || '(anonymous)',
|
|
||||||
lineNumber: frame.lineNumber,
|
|
||||||
columnNumber: frame.columnNumber,
|
|
||||||
selfMs: 0,
|
|
||||||
samples: 0,
|
|
||||||
};
|
|
||||||
row.selfMs += delta / 1000;
|
|
||||||
row.samples += 1;
|
|
||||||
totals.set(key, row);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
totalProfileMs: totalUs / 1000,
|
|
||||||
topSelfTime: [...totals.values()].sort((a, b) => b.selfMs - a.selfMs).slice(0, 15),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function installProbe(context) {
|
|
||||||
await context.addInitScript(() => {
|
|
||||||
const originalAdd = EventTarget.prototype.addEventListener;
|
|
||||||
const originalRemove = EventTarget.prototype.removeEventListener;
|
|
||||||
const listenerRows = new Map();
|
|
||||||
|
|
||||||
function targetName(target) {
|
|
||||||
if (target === window) return 'window';
|
|
||||||
if (target === document) return 'document';
|
|
||||||
if (target?.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
const tag = target.tagName?.toLowerCase() || 'element';
|
|
||||||
return target.id ? `${tag}#${target.id}` : tag;
|
|
||||||
}
|
|
||||||
return target?.constructor?.name || String(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stack() {
|
|
||||||
return String(new Error().stack || '')
|
|
||||||
.split('\n')
|
|
||||||
.slice(3, 7)
|
|
||||||
.map((line) => line.trim().replace(location.origin, ''))
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function note(kind, target, type) {
|
|
||||||
const key = `${targetName(target)}::${type}::${stack()}`;
|
|
||||||
const row = listenerRows.get(key) || {
|
|
||||||
target: targetName(target),
|
|
||||||
type: String(type),
|
|
||||||
stack: stack(),
|
|
||||||
adds: 0,
|
|
||||||
removes: 0,
|
|
||||||
};
|
|
||||||
row[kind] += 1;
|
|
||||||
listenerRows.set(key, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
EventTarget.prototype.addEventListener = function patchedAdd(type, listener, options) {
|
|
||||||
note('adds', this, type);
|
|
||||||
return originalAdd.call(this, type, listener, options);
|
|
||||||
};
|
|
||||||
EventTarget.prototype.removeEventListener = function patchedRemove(type, listener, options) {
|
|
||||||
note('removes', this, type);
|
|
||||||
return originalRemove.call(this, type, listener, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.__sweepProbe = {
|
|
||||||
longTasks: [],
|
|
||||||
frames: [],
|
|
||||||
intervalDrifts: [],
|
|
||||||
listenerSnapshot() {
|
|
||||||
return [...listenerRows.values()]
|
|
||||||
.map((row) => ({ ...row, churn: row.adds + row.removes }))
|
|
||||||
.sort((a, b) => b.churn - a.churn)
|
|
||||||
.slice(0, 20);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
new PerformanceObserver((list) => {
|
|
||||||
for (const entry of list.getEntries()) {
|
|
||||||
window.__sweepProbe.longTasks.push({
|
|
||||||
name: entry.name,
|
|
||||||
startTime: entry.startTime,
|
|
||||||
duration: entry.duration,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).observe({ type: 'longtask', buffered: true });
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
let lastFrame = performance.now();
|
|
||||||
const frameLoop = (now) => {
|
|
||||||
window.__sweepProbe.frames.push(now - lastFrame);
|
|
||||||
lastFrame = now;
|
|
||||||
requestAnimationFrame(frameLoop);
|
|
||||||
};
|
|
||||||
requestAnimationFrame(frameLoop);
|
|
||||||
|
|
||||||
let lastTick = performance.now();
|
|
||||||
setInterval(() => {
|
|
||||||
const now = performance.now();
|
|
||||||
window.__sweepProbe.intervalDrifts.push(now - lastTick - 250);
|
|
||||||
lastTick = now;
|
|
||||||
}, 250);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runRoute(browser, route) {
|
|
||||||
const context = await browser.newContext({
|
|
||||||
viewport: { width: viewport[0] || 390, height: viewport[1] || 844 },
|
|
||||||
deviceScaleFactor: isMobile ? 3 : 1,
|
|
||||||
isMobile,
|
|
||||||
hasTouch: isMobile,
|
|
||||||
ignoreHTTPSErrors: true,
|
|
||||||
});
|
|
||||||
await installProbe(context);
|
|
||||||
const page = await context.newPage();
|
|
||||||
const cdp = await context.newCDPSession(page);
|
|
||||||
await cdp.send('Performance.enable');
|
|
||||||
await cdp.send('Profiler.enable');
|
|
||||||
if (cpuThrottle > 1) {
|
|
||||||
await cdp.send('Emulation.setCPUThrottlingRate', { rate: cpuThrottle });
|
|
||||||
}
|
|
||||||
|
|
||||||
const consoleErrors = [];
|
|
||||||
const errorResponses = [];
|
|
||||||
page.on('console', (message) => {
|
|
||||||
if (['error', 'warning'].includes(message.type())) {
|
|
||||||
consoleErrors.push({ type: message.type(), text: message.text().slice(0, 500) });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
page.on('response', (response) => {
|
|
||||||
if (response.status() >= 400) {
|
|
||||||
errorResponses.push({ url: response.url(), status: response.status() });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const url = new URL(route, baseUrl).toString();
|
|
||||||
const startWall = Date.now();
|
|
||||||
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 90000 });
|
|
||||||
await page.waitForLoadState('load', { timeout: 90000 }).catch(() => {});
|
|
||||||
await sleep(4000);
|
|
||||||
const startMetrics = interestingMetrics(await cdp.send('Performance.getMetrics'));
|
|
||||||
await cdp.send('Profiler.start');
|
|
||||||
await sleep(sampleMs / 2);
|
|
||||||
await page.mouse.wheel(0, 900).catch(() => {});
|
|
||||||
await sleep(sampleMs / 2);
|
|
||||||
const { profile } = await cdp.send('Profiler.stop');
|
|
||||||
const endMetrics = interestingMetrics(await cdp.send('Performance.getMetrics'));
|
|
||||||
const probe = await page.evaluate(() => ({
|
|
||||||
longTasks: window.__sweepProbe.longTasks,
|
|
||||||
frames: window.__sweepProbe.frames.slice(5),
|
|
||||||
intervalDrifts: window.__sweepProbe.intervalDrifts,
|
|
||||||
listeners: window.__sweepProbe.listenerSnapshot(),
|
|
||||||
}));
|
|
||||||
const screenshotName = route === '/' ? 'root' : route.replace(/^\//, '').replace(/\W+/g, '-');
|
|
||||||
await page.screenshot({ path: path.join(runDir, `${screenshotName || 'root'}.png`), fullPage: true });
|
|
||||||
await context.close();
|
|
||||||
|
|
||||||
const delta = Object.fromEntries(
|
|
||||||
Object.keys(endMetrics).map((key) => [key, endMetrics[key] - (startMetrics[key] || 0)]),
|
|
||||||
);
|
|
||||||
const frames = probe.frames;
|
|
||||||
const longTasks = probe.longTasks.sort((a, b) => b.duration - a.duration);
|
|
||||||
return {
|
|
||||||
route,
|
|
||||||
finalUrl: page.url(),
|
|
||||||
elapsedMs: Date.now() - startWall,
|
|
||||||
metrics: { start: startMetrics, end: endMetrics, delta },
|
|
||||||
cpu: summarizeCpuProfile(profile),
|
|
||||||
responsiveness: {
|
|
||||||
longTaskCount: longTasks.length,
|
|
||||||
worstLongTaskMs: longTasks[0]?.duration || 0,
|
|
||||||
topLongTasks: longTasks.slice(0, 8),
|
|
||||||
frameCount: frames.length,
|
|
||||||
averageFrameMs: frames.reduce((sum, value) => sum + value, 0) / frames.length,
|
|
||||||
p95FrameMs: percentile(frames, 0.95),
|
|
||||||
p99FrameMs: percentile(frames, 0.99),
|
|
||||||
intervalDriftP95Ms: percentile(probe.intervalDrifts, 0.95),
|
|
||||||
intervalDriftP99Ms: percentile(probe.intervalDrifts, 0.99),
|
|
||||||
},
|
|
||||||
listenerChurn: probe.listeners,
|
|
||||||
consoleErrors,
|
|
||||||
errorResponses,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
await fs.mkdir(runDir, { recursive: true });
|
|
||||||
const browser = await chromium.launch({
|
|
||||||
headless: true,
|
|
||||||
args: ['--autoplay-policy=no-user-gesture-required'],
|
|
||||||
});
|
|
||||||
const results = [];
|
|
||||||
for (const route of routes) {
|
|
||||||
console.error(`profiling ${route} at ${cpuThrottle}x`);
|
|
||||||
results.push(await runRoute(browser, route));
|
|
||||||
}
|
|
||||||
await browser.close();
|
|
||||||
|
|
||||||
const report = {
|
|
||||||
baseUrl,
|
|
||||||
runDir,
|
|
||||||
environment: {
|
|
||||||
cpuThrottle,
|
|
||||||
viewport: { width: viewport[0] || 390, height: viewport[1] || 844 },
|
|
||||||
isMobile,
|
|
||||||
sampleMs,
|
|
||||||
routes,
|
|
||||||
},
|
|
||||||
results,
|
|
||||||
rankedByAverageFrame: [...results].sort(
|
|
||||||
(a, b) => b.responsiveness.averageFrameMs - a.responsiveness.averageFrameMs,
|
|
||||||
),
|
|
||||||
rankedByTaskDuration: [...results].sort(
|
|
||||||
(a, b) => b.metrics.delta.TaskDuration - a.metrics.delta.TaskDuration,
|
|
||||||
),
|
|
||||||
rankedByListenerChurn: [...results].sort((a, b) => {
|
|
||||||
const churnA = a.listenerChurn.reduce((sum, row) => sum + row.churn, 0);
|
|
||||||
const churnB = b.listenerChurn.reduce((sum, row) => sum + row.churn, 0);
|
|
||||||
return churnB - churnA;
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
await fs.writeFile(path.join(runDir, 'route-sweep-report.json'), JSON.stringify(report, null, 2));
|
|
||||||
console.log(JSON.stringify(report, null, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
process.exitCode = 1;
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"playwright": "^1.60.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 395 KiB |
|
Before Width: | Height: | Size: 390 KiB |
@@ -1,334 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "https://rover.otter.land",
|
|
||||||
"runDir": "perf/results/2026-06-11T03-02-07-015Z",
|
|
||||||
"elapsedMs": 38584,
|
|
||||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36",
|
|
||||||
"title": "Roomba Rover",
|
|
||||||
"finalUrl": "https://rover.otter.land/",
|
|
||||||
"metrics": {
|
|
||||||
"afterLoad": {
|
|
||||||
"Timestamp": 38634.714754,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 66899,
|
|
||||||
"Nodes": 2977,
|
|
||||||
"LayoutCount": 89,
|
|
||||||
"RecalcStyleCount": 274,
|
|
||||||
"LayoutDuration": 0.106766,
|
|
||||||
"RecalcStyleDuration": 0.065778,
|
|
||||||
"ScriptDuration": 4.06233,
|
|
||||||
"TaskDuration": 4.933287,
|
|
||||||
"JSHeapUsedSize": 35455032,
|
|
||||||
"JSHeapTotalSize": 80216064
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"Timestamp": 38667.823166,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 75714,
|
|
||||||
"Nodes": 2955,
|
|
||||||
"LayoutCount": 654,
|
|
||||||
"RecalcStyleCount": 2517,
|
|
||||||
"LayoutDuration": 0.32419,
|
|
||||||
"RecalcStyleDuration": 0.312982,
|
|
||||||
"ScriptDuration": 32.06036,
|
|
||||||
"TaskDuration": 37.597822,
|
|
||||||
"JSHeapUsedSize": 39685520,
|
|
||||||
"JSHeapTotalSize": 86081536
|
|
||||||
},
|
|
||||||
"delta": {
|
|
||||||
"Timestamp": 33.10841200000141,
|
|
||||||
"Documents": 0,
|
|
||||||
"Frames": 0,
|
|
||||||
"JSEventListeners": 8815,
|
|
||||||
"Nodes": -22,
|
|
||||||
"LayoutCount": 565,
|
|
||||||
"RecalcStyleCount": 2243,
|
|
||||||
"LayoutDuration": 0.21742399999999998,
|
|
||||||
"RecalcStyleDuration": 0.24720399999999998,
|
|
||||||
"ScriptDuration": 27.998030000000004,
|
|
||||||
"TaskDuration": 32.664535,
|
|
||||||
"JSHeapUsedSize": 4230488,
|
|
||||||
"JSHeapTotalSize": 5865472
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cpuSummary": {
|
|
||||||
"totalProfileMs": 32896.945,
|
|
||||||
"sampleCount": 30557,
|
|
||||||
"topSelfTime": [
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(program)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 4657.831000000009,
|
|
||||||
"samples": 4273
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "C9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 94304,
|
|
||||||
"selfMs": 2470.519000000005,
|
|
||||||
"samples": 2297
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "ad",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 7898,
|
|
||||||
"selfMs": 2383.6760000000036,
|
|
||||||
"samples": 2219
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "removeEventListener",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 1596.3859999999981,
|
|
||||||
"samples": 1476
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "addEventListener",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 1314.546999999998,
|
|
||||||
"samples": 1219
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "T9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 115769,
|
|
||||||
"selfMs": 1280.09,
|
|
||||||
"samples": 1195
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Q",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 39681,
|
|
||||||
"selfMs": 1246.5730000000015,
|
|
||||||
"samples": 1160
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "va",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 10653,
|
|
||||||
"selfMs": 1170.771,
|
|
||||||
"samples": 1088
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Fl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 101199,
|
|
||||||
"selfMs": 1028.4150000000006,
|
|
||||||
"samples": 954
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "n2",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 42234,
|
|
||||||
"selfMs": 1007.8280000000008,
|
|
||||||
"samples": 938
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "w1",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 40723,
|
|
||||||
"selfMs": 992.6080000000019,
|
|
||||||
"samples": 927
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "zl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 80827,
|
|
||||||
"selfMs": 975.6390000000019,
|
|
||||||
"samples": 905
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "v9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 71024,
|
|
||||||
"selfMs": 909.7280000000015,
|
|
||||||
"samples": 848
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "(anonymous)",
|
|
||||||
"lineNumber": 17,
|
|
||||||
"columnNumber": 5982,
|
|
||||||
"selfMs": 710.5740000000005,
|
|
||||||
"samples": 662
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Eh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 95724,
|
|
||||||
"selfMs": 656.2710000000002,
|
|
||||||
"samples": 613
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "w4",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 91309,
|
|
||||||
"selfMs": 544.7980000000002,
|
|
||||||
"samples": 508
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Al",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 97450,
|
|
||||||
"selfMs": 485.71200000000005,
|
|
||||||
"samples": 453
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "kc",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 1388916,
|
|
||||||
"selfMs": 482.39700000000016,
|
|
||||||
"samples": 450
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "B6",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 34145,
|
|
||||||
"selfMs": 475.1790000000002,
|
|
||||||
"samples": 446
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "o1",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 70367,
|
|
||||||
"selfMs": 441.8229999999999,
|
|
||||||
"samples": 412
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "lt",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 7349,
|
|
||||||
"selfMs": 398.3480000000001,
|
|
||||||
"samples": 369
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "r",
|
|
||||||
"lineNumber": 1,
|
|
||||||
"columnNumber": 967,
|
|
||||||
"selfMs": 348.187,
|
|
||||||
"samples": 326
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(idle)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 319.7700000000001,
|
|
||||||
"samples": 297
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "X",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 40176,
|
|
||||||
"selfMs": 283.33400000000006,
|
|
||||||
"samples": 264
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "a2",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 71,
|
|
||||||
"selfMs": 258.0969999999999,
|
|
||||||
"samples": 239
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "n9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 56257,
|
|
||||||
"selfMs": 216.19699999999995,
|
|
||||||
"samples": 202
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(garbage collector)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 214.13299999999995,
|
|
||||||
"samples": 195
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "t9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 50605,
|
|
||||||
"selfMs": 207.45599999999985,
|
|
||||||
"samples": 194
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "now",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 202.39299999999992,
|
|
||||||
"samples": 189
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "po",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 214243,
|
|
||||||
"selfMs": 192.35299999999984,
|
|
||||||
"samples": 178
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"responsiveness": {
|
|
||||||
"longTaskCount": 1,
|
|
||||||
"topLongTasks": [
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 5330.30000000447,
|
|
||||||
"duration": 83
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frameCount": 2261,
|
|
||||||
"averageFrameMs": 16.83551525873507,
|
|
||||||
"p95FrameMs": 16.799999999999272,
|
|
||||||
"p99FrameMs": 16.80000000000291,
|
|
||||||
"intervalDriftCount": 152,
|
|
||||||
"averageIntervalDriftMs": 0.02039473680289168,
|
|
||||||
"p95IntervalDriftMs": 4.600000001490116,
|
|
||||||
"p99IntervalDriftMs": 23.399999998509884
|
|
||||||
},
|
|
||||||
"consoleMessages": [
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"text": "[socket] connecting to https://rover.otter.land",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63768,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63768
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"failedRequests": [],
|
|
||||||
"errorResponses": []
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 402 KiB |
|
Before Width: | Height: | Size: 328 KiB |
|
Before Width: | Height: | Size: 360 KiB |
@@ -1,423 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "https://rover.otter.land",
|
|
||||||
"runDir": "perf/results/2026-06-11T03-04-44-940Z",
|
|
||||||
"elapsedMs": 41919,
|
|
||||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36",
|
|
||||||
"title": "Roomba Rover",
|
|
||||||
"finalUrl": "https://rover.otter.land/",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 4,
|
|
||||||
"viewport": {
|
|
||||||
"width": 1366,
|
|
||||||
"height": 768
|
|
||||||
},
|
|
||||||
"isMobile": false
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"afterLoad": {
|
|
||||||
"Timestamp": 38792.763829,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 6177,
|
|
||||||
"Nodes": 2848,
|
|
||||||
"LayoutCount": 38,
|
|
||||||
"RecalcStyleCount": 43,
|
|
||||||
"LayoutDuration": 0.233029,
|
|
||||||
"RecalcStyleDuration": 0.13828,
|
|
||||||
"ScriptDuration": 3.172814,
|
|
||||||
"TaskDuration": 4.881969,
|
|
||||||
"JSHeapUsedSize": 17107224,
|
|
||||||
"JSHeapTotalSize": 28311552
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"Timestamp": 38828.522855,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 3175,
|
|
||||||
"Nodes": 3046,
|
|
||||||
"LayoutCount": 767,
|
|
||||||
"RecalcStyleCount": 773,
|
|
||||||
"LayoutDuration": 1.403177,
|
|
||||||
"RecalcStyleDuration": 0.88412,
|
|
||||||
"ScriptDuration": 24.747626,
|
|
||||||
"TaskDuration": 39.759494,
|
|
||||||
"JSHeapUsedSize": 41718720,
|
|
||||||
"JSHeapTotalSize": 81625088
|
|
||||||
},
|
|
||||||
"delta": {
|
|
||||||
"Timestamp": 35.75902599999972,
|
|
||||||
"Documents": 0,
|
|
||||||
"Frames": 0,
|
|
||||||
"JSEventListeners": -3002,
|
|
||||||
"Nodes": 198,
|
|
||||||
"LayoutCount": 729,
|
|
||||||
"RecalcStyleCount": 730,
|
|
||||||
"LayoutDuration": 1.170148,
|
|
||||||
"RecalcStyleDuration": 0.7458400000000001,
|
|
||||||
"ScriptDuration": 21.574812,
|
|
||||||
"TaskDuration": 34.877525,
|
|
||||||
"JSHeapUsedSize": 24611496,
|
|
||||||
"JSHeapTotalSize": 53313536
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cpuSummary": {
|
|
||||||
"totalProfileMs": 34677.553,
|
|
||||||
"sampleCount": 31910,
|
|
||||||
"topSelfTime": [
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(program)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 12142.741999999993,
|
|
||||||
"samples": 10968
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "C9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 94304,
|
|
||||||
"selfMs": 1976.3040000000042,
|
|
||||||
"samples": 1842
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "ad",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 7898,
|
|
||||||
"selfMs": 1545.372000000002,
|
|
||||||
"samples": 1431
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "(anonymous)",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 1532780,
|
|
||||||
"selfMs": 974.0629999999992,
|
|
||||||
"samples": 908
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "v9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 71024,
|
|
||||||
"selfMs": 844.4459999999995,
|
|
||||||
"samples": 805
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Q",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 39681,
|
|
||||||
"selfMs": 841.3410000000006,
|
|
||||||
"samples": 763
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Fl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 101199,
|
|
||||||
"selfMs": 826.49,
|
|
||||||
"samples": 777
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "w1",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 40723,
|
|
||||||
"selfMs": 811.0100000000001,
|
|
||||||
"samples": 754
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "removeEventListener",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 811.0059999999984,
|
|
||||||
"samples": 723
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "va",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 10653,
|
|
||||||
"selfMs": 752.6890000000008,
|
|
||||||
"samples": 698
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(idle)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 664.0250000000005,
|
|
||||||
"samples": 625
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "zl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 80827,
|
|
||||||
"selfMs": 596.2229999999997,
|
|
||||||
"samples": 556
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "addEventListener",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 573.276,
|
|
||||||
"samples": 544
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Eh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 95724,
|
|
||||||
"selfMs": 515.44,
|
|
||||||
"samples": 477
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Kl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 117211,
|
|
||||||
"selfMs": 499.10000000000025,
|
|
||||||
"samples": 470
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Al",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 97450,
|
|
||||||
"selfMs": 497.787,
|
|
||||||
"samples": 453
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "n2",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 42234,
|
|
||||||
"selfMs": 408.08700000000005,
|
|
||||||
"samples": 381
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "o1",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 70367,
|
|
||||||
"selfMs": 381.6320000000001,
|
|
||||||
"samples": 359
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "r",
|
|
||||||
"lineNumber": 1,
|
|
||||||
"columnNumber": 967,
|
|
||||||
"selfMs": 345.4999999999999,
|
|
||||||
"samples": 328
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Oh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 116246,
|
|
||||||
"selfMs": 334.56799999999987,
|
|
||||||
"samples": 313
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "a2",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 71,
|
|
||||||
"selfMs": 314.36699999999996,
|
|
||||||
"samples": 293
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "lt",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 7349,
|
|
||||||
"selfMs": 313.014,
|
|
||||||
"samples": 286
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Z4",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 28258,
|
|
||||||
"selfMs": 290.6389999999998,
|
|
||||||
"samples": 261
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(garbage collector)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 236.61700000000016,
|
|
||||||
"samples": 210
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "B6",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 34145,
|
|
||||||
"selfMs": 229.56900000000002,
|
|
||||||
"samples": 213
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "t9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 50605,
|
|
||||||
"selfMs": 208.06900000000002,
|
|
||||||
"samples": 191
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "Blob",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 179.771,
|
|
||||||
"samples": 158
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "w4",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 91309,
|
|
||||||
"selfMs": 147.32200000000006,
|
|
||||||
"samples": 132
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "n9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 56257,
|
|
||||||
"selfMs": 135.37199999999999,
|
|
||||||
"samples": 125
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "ma",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 10401,
|
|
||||||
"selfMs": 129.19600000000003,
|
|
||||||
"samples": 118
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"responsiveness": {
|
|
||||||
"longTaskCount": 14,
|
|
||||||
"topLongTasks": [
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 193.60000000149012,
|
|
||||||
"duration": 184
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 381.20000000298023,
|
|
||||||
"duration": 137
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 23610.70000000298,
|
|
||||||
"duration": 131
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1045,
|
|
||||||
"duration": 130
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1972.2999999970198,
|
|
||||||
"duration": 107
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 6042.70000000298,
|
|
||||||
"duration": 105
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1874.3999999985099,
|
|
||||||
"duration": 97
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 2697.7000000029802,
|
|
||||||
"duration": 96
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 518.2999999970198,
|
|
||||||
"duration": 81
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 2618.5,
|
|
||||||
"duration": 79
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 839.3999999985099,
|
|
||||||
"duration": 75
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 974.1000000014901,
|
|
||||||
"duration": 70
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 770.7999999970198,
|
|
||||||
"duration": 68
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 39623.79999999702,
|
|
||||||
"duration": 54
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frameCount": 577,
|
|
||||||
"averageFrameMs": 69.98561525129982,
|
|
||||||
"p95FrameMs": 133.29999999999927,
|
|
||||||
"p99FrameMs": 166.70000000000027,
|
|
||||||
"intervalDriftCount": 159,
|
|
||||||
"averageIntervalDriftMs": 6.808805031455913,
|
|
||||||
"p95IntervalDriftMs": 83.19999999552965,
|
|
||||||
"p99IntervalDriftMs": 239.80000000447035
|
|
||||||
},
|
|
||||||
"consoleMessages": [
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"text": "[socket] connecting to https://rover.otter.land",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63768,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63768
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 404 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"failedRequests": [],
|
|
||||||
"errorResponses": [
|
|
||||||
{
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"status": 404
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 226 KiB |
@@ -1,403 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "https://rover.otter.land",
|
|
||||||
"runDir": "perf/results/2026-06-11T03-05-35-042Z",
|
|
||||||
"elapsedMs": 41711,
|
|
||||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36",
|
|
||||||
"title": "Roomba Rover",
|
|
||||||
"finalUrl": "https://rover.otter.land/",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 4,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"afterLoad": {
|
|
||||||
"Timestamp": 38842.912408,
|
|
||||||
"Documents": 3,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 5426,
|
|
||||||
"Nodes": 2393,
|
|
||||||
"LayoutCount": 100,
|
|
||||||
"RecalcStyleCount": 106,
|
|
||||||
"LayoutDuration": 0.207151,
|
|
||||||
"RecalcStyleDuration": 0.170546,
|
|
||||||
"ScriptDuration": 3.283922,
|
|
||||||
"TaskDuration": 5.024573,
|
|
||||||
"JSHeapUsedSize": 12044476,
|
|
||||||
"JSHeapTotalSize": 27000832
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"Timestamp": 38878.342222,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 40107,
|
|
||||||
"Nodes": 2536,
|
|
||||||
"LayoutCount": 1291,
|
|
||||||
"RecalcStyleCount": 1289,
|
|
||||||
"LayoutDuration": 1.04779,
|
|
||||||
"RecalcStyleDuration": 1.180594,
|
|
||||||
"ScriptDuration": 24.454767,
|
|
||||||
"TaskDuration": 39.786697,
|
|
||||||
"JSHeapUsedSize": 17640640,
|
|
||||||
"JSHeapTotalSize": 82411520
|
|
||||||
},
|
|
||||||
"delta": {
|
|
||||||
"Timestamp": 35.42981400000281,
|
|
||||||
"Documents": -2,
|
|
||||||
"Frames": 0,
|
|
||||||
"JSEventListeners": 34681,
|
|
||||||
"Nodes": 143,
|
|
||||||
"LayoutCount": 1191,
|
|
||||||
"RecalcStyleCount": 1183,
|
|
||||||
"LayoutDuration": 0.840639,
|
|
||||||
"RecalcStyleDuration": 1.0100479999999998,
|
|
||||||
"ScriptDuration": 21.170845,
|
|
||||||
"TaskDuration": 34.762124,
|
|
||||||
"JSHeapUsedSize": 5596164,
|
|
||||||
"JSHeapTotalSize": 55410688
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cpuSummary": {
|
|
||||||
"totalProfileMs": 34204.39,
|
|
||||||
"sampleCount": 31429,
|
|
||||||
"topSelfTime": [
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(program)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 12727.823999999955,
|
|
||||||
"samples": 11515
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "C9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 94304,
|
|
||||||
"selfMs": 1738.6469999999997,
|
|
||||||
"samples": 1612
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "ad",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 7898,
|
|
||||||
"selfMs": 1547.8840000000005,
|
|
||||||
"samples": 1425
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "removeEventListener",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 1048.3049999999994,
|
|
||||||
"samples": 922
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Fl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 101199,
|
|
||||||
"selfMs": 1013.1350000000006,
|
|
||||||
"samples": 958
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "w1",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 40723,
|
|
||||||
"selfMs": 846.9869999999997,
|
|
||||||
"samples": 777
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "va",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 10653,
|
|
||||||
"selfMs": 826.692,
|
|
||||||
"samples": 756
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Q",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 39681,
|
|
||||||
"selfMs": 813.2879999999994,
|
|
||||||
"samples": 744
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "v9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 71024,
|
|
||||||
"selfMs": 764.6159999999996,
|
|
||||||
"samples": 716
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "addEventListener",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 711.6550000000001,
|
|
||||||
"samples": 655
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "(anonymous)",
|
|
||||||
"lineNumber": 17,
|
|
||||||
"columnNumber": 9774,
|
|
||||||
"selfMs": 643.1059999999997,
|
|
||||||
"samples": 593
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Eh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 95724,
|
|
||||||
"selfMs": 609.4899999999997,
|
|
||||||
"samples": 573
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "zl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 80827,
|
|
||||||
"selfMs": 558.8860000000001,
|
|
||||||
"samples": 525
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Kl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 117211,
|
|
||||||
"selfMs": 555.4119999999999,
|
|
||||||
"samples": 516
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(idle)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 476.40799999999973,
|
|
||||||
"samples": 445
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Al",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 97450,
|
|
||||||
"selfMs": 392.31999999999965,
|
|
||||||
"samples": 400
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "kc",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 1388916,
|
|
||||||
"selfMs": 363.94399999999945,
|
|
||||||
"samples": 336
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "n2",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 42234,
|
|
||||||
"selfMs": 356.98299999999995,
|
|
||||||
"samples": 334
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Z4",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 28258,
|
|
||||||
"selfMs": 322.32900000000024,
|
|
||||||
"samples": 293
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "lt",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 7349,
|
|
||||||
"selfMs": 291.17899999999986,
|
|
||||||
"samples": 268
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "o1",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 70367,
|
|
||||||
"selfMs": 269.2040000000001,
|
|
||||||
"samples": 257
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "B6",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 34145,
|
|
||||||
"selfMs": 248.9579999999999,
|
|
||||||
"samples": 228
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "r",
|
|
||||||
"lineNumber": 1,
|
|
||||||
"columnNumber": 967,
|
|
||||||
"selfMs": 246.97999999999996,
|
|
||||||
"samples": 223
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Oh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 116246,
|
|
||||||
"selfMs": 239.94800000000004,
|
|
||||||
"samples": 223
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "a2",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 71,
|
|
||||||
"selfMs": 219.92599999999987,
|
|
||||||
"samples": 209
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "t9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 50605,
|
|
||||||
"selfMs": 170.78300000000004,
|
|
||||||
"samples": 157
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "N7",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 100487,
|
|
||||||
"selfMs": 150.48000000000005,
|
|
||||||
"samples": 144
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "w4",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 91309,
|
|
||||||
"selfMs": 144.64899999999994,
|
|
||||||
"samples": 138
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Td.k1.createElement",
|
|
||||||
"lineNumber": 1,
|
|
||||||
"columnNumber": 6938,
|
|
||||||
"selfMs": 138.19599999999997,
|
|
||||||
"samples": 132
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "n9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 56257,
|
|
||||||
"selfMs": 135.858,
|
|
||||||
"samples": 128
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"responsiveness": {
|
|
||||||
"longTaskCount": 10,
|
|
||||||
"topLongTasks": [
|
|
||||||
{
|
|
||||||
"name": "unknown",
|
|
||||||
"startTime": 5777.89999999851,
|
|
||||||
"duration": 234
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 185,
|
|
||||||
"duration": 188
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 23461.59999999404,
|
|
||||||
"duration": 179
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 957.7999999970198,
|
|
||||||
"duration": 111
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 376.59999999403954,
|
|
||||||
"duration": 106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1511.7999999970198,
|
|
||||||
"duration": 83
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 696.7999999970198,
|
|
||||||
"duration": 73
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 770.7999999970198,
|
|
||||||
"duration": 69
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 482.59999999403954,
|
|
||||||
"duration": 67
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 892.6999999955297,
|
|
||||||
"duration": 64
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frameCount": 1031,
|
|
||||||
"averageFrameMs": 38.92502424830261,
|
|
||||||
"p95FrameMs": 66.70000000000073,
|
|
||||||
"p99FrameMs": 133.3000000000029,
|
|
||||||
"intervalDriftCount": 157,
|
|
||||||
"averageIntervalDriftMs": 8.188535031856626,
|
|
||||||
"p95IntervalDriftMs": 62.100000001490116,
|
|
||||||
"p99IntervalDriftMs": 431.09999999403954
|
|
||||||
},
|
|
||||||
"consoleMessages": [
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"text": "[socket] connecting to https://rover.otter.land",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63768,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63768
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 404 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"failedRequests": [],
|
|
||||||
"errorResponses": [
|
|
||||||
{
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"status": 404
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 604 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 5.8 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
@@ -1,527 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "https://rover.otter.land/spectate",
|
|
||||||
"runDir": "perf/results/2026-06-11T03-14-34-168Z",
|
|
||||||
"elapsedMs": 64190,
|
|
||||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36",
|
|
||||||
"title": "Roomba Rover",
|
|
||||||
"finalUrl": "https://rover.otter.land/spectate",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 8,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"afterLoad": {
|
|
||||||
"Timestamp": 39382.268457,
|
|
||||||
"Documents": 3,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 494,
|
|
||||||
"Nodes": 8431,
|
|
||||||
"LayoutCount": 16,
|
|
||||||
"RecalcStyleCount": 18,
|
|
||||||
"LayoutDuration": 0.410692,
|
|
||||||
"RecalcStyleDuration": 0.442447,
|
|
||||||
"ScriptDuration": 2.378027,
|
|
||||||
"TaskDuration": 5.043961,
|
|
||||||
"JSHeapUsedSize": 14710548,
|
|
||||||
"JSHeapTotalSize": 20447232
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"Timestamp": 39435.115434,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 509,
|
|
||||||
"Nodes": 12246,
|
|
||||||
"LayoutCount": 532,
|
|
||||||
"RecalcStyleCount": 540,
|
|
||||||
"LayoutDuration": 2.892458,
|
|
||||||
"RecalcStyleDuration": 6.325277,
|
|
||||||
"ScriptDuration": 21.960492,
|
|
||||||
"TaskDuration": 54.170961,
|
|
||||||
"JSHeapUsedSize": 14649044,
|
|
||||||
"JSHeapTotalSize": 65634304
|
|
||||||
},
|
|
||||||
"delta": {
|
|
||||||
"Timestamp": 52.84697700000106,
|
|
||||||
"Documents": -2,
|
|
||||||
"Frames": 0,
|
|
||||||
"JSEventListeners": 15,
|
|
||||||
"Nodes": 3815,
|
|
||||||
"LayoutCount": 516,
|
|
||||||
"RecalcStyleCount": 522,
|
|
||||||
"LayoutDuration": 2.481766,
|
|
||||||
"RecalcStyleDuration": 5.88283,
|
|
||||||
"ScriptDuration": 19.582465,
|
|
||||||
"TaskDuration": 49.126999999999995,
|
|
||||||
"JSHeapUsedSize": -61504,
|
|
||||||
"JSHeapTotalSize": 45187072
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cpuSummary": {
|
|
||||||
"totalProfileMs": 41344.156,
|
|
||||||
"sampleCount": 31576,
|
|
||||||
"topSelfTime": [
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(program)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 21236.87599999999,
|
|
||||||
"samples": 16120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(idle)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 2981.150999999998,
|
|
||||||
"samples": 2232
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "(anonymous)",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 64833,
|
|
||||||
"selfMs": 1365.8530000000014,
|
|
||||||
"samples": 1053
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "C9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 94304,
|
|
||||||
"selfMs": 1234.6300000000003,
|
|
||||||
"samples": 936
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "ad",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 7898,
|
|
||||||
"selfMs": 842.8760000000004,
|
|
||||||
"samples": 664
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Fl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 101199,
|
|
||||||
"selfMs": 674.3050000000004,
|
|
||||||
"samples": 527
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "w1",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 40723,
|
|
||||||
"selfMs": 621.5390000000002,
|
|
||||||
"samples": 475
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Q",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 39681,
|
|
||||||
"selfMs": 505.4100000000001,
|
|
||||||
"samples": 396
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "Blob",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 479.50199999999967,
|
|
||||||
"samples": 331
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Eh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 95724,
|
|
||||||
"selfMs": 423.5530000000003,
|
|
||||||
"samples": 343
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "decodeString",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 48609,
|
|
||||||
"selfMs": 423.2989999999997,
|
|
||||||
"samples": 328
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "B6",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 34145,
|
|
||||||
"selfMs": 404.793,
|
|
||||||
"samples": 311
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Kl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 117211,
|
|
||||||
"selfMs": 398.8710000000001,
|
|
||||||
"samples": 291
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "setAttribute",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 384.0259999999998,
|
|
||||||
"samples": 319
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "zl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 80827,
|
|
||||||
"selfMs": 372.5609999999999,
|
|
||||||
"samples": 298
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Al",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 97450,
|
|
||||||
"selfMs": 370.4030000000001,
|
|
||||||
"samples": 280
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "n2",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 42234,
|
|
||||||
"selfMs": 356.20099999999996,
|
|
||||||
"samples": 280
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Kt",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 48095,
|
|
||||||
"selfMs": 303.89100000000013,
|
|
||||||
"samples": 222
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "va",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 10653,
|
|
||||||
"selfMs": 289.63100000000003,
|
|
||||||
"samples": 227
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(garbage collector)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 257.03600000000006,
|
|
||||||
"samples": 183
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "createObjectURL",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 223.09499999999994,
|
|
||||||
"samples": 181
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "r",
|
|
||||||
"lineNumber": 1,
|
|
||||||
"columnNumber": 967,
|
|
||||||
"selfMs": 212.97800000000012,
|
|
||||||
"samples": 168
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "w4",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 91309,
|
|
||||||
"selfMs": 202.98600000000016,
|
|
||||||
"samples": 154
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "removeChild",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 197.55400000000006,
|
|
||||||
"samples": 144
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "v9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 71024,
|
|
||||||
"selfMs": 175.75600000000009,
|
|
||||||
"samples": 132
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "X",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 40176,
|
|
||||||
"selfMs": 166.36100000000005,
|
|
||||||
"samples": 136
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "o1",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 70367,
|
|
||||||
"selfMs": 162.70300000000003,
|
|
||||||
"samples": 132
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "V",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 38470,
|
|
||||||
"selfMs": 159.72000000000003,
|
|
||||||
"samples": 124
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Oh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 116246,
|
|
||||||
"selfMs": 151.22299999999993,
|
|
||||||
"samples": 109
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "V8",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 1487297,
|
|
||||||
"selfMs": 147.01899999999992,
|
|
||||||
"samples": 113
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"responsiveness": {
|
|
||||||
"longTaskCount": 322,
|
|
||||||
"topLongTasks": [
|
|
||||||
{
|
|
||||||
"name": "unknown",
|
|
||||||
"startTime": 12297.80000000447,
|
|
||||||
"duration": 518
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 250.39999999850988,
|
|
||||||
"duration": 401
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "unknown",
|
|
||||||
"startTime": 12929.20000000298,
|
|
||||||
"duration": 308
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1907.3999999985099,
|
|
||||||
"duration": 274
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 36069.80000000447,
|
|
||||||
"duration": 230
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 2530.89999999851,
|
|
||||||
"duration": 195
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 54969,
|
|
||||||
"duration": 190
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 55840.80000000447,
|
|
||||||
"duration": 189
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 24437.39999999851,
|
|
||||||
"duration": 188
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 47105.5,
|
|
||||||
"duration": 172
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1736,
|
|
||||||
"duration": 170
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 2367.60000000149,
|
|
||||||
"duration": 163
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1320.3000000044703,
|
|
||||||
"duration": 161
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 659.6000000014901,
|
|
||||||
"duration": 159
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 30711.60000000149,
|
|
||||||
"duration": 146
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 54631.5,
|
|
||||||
"duration": 146
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 3079,
|
|
||||||
"duration": 139
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 819.2000000029802,
|
|
||||||
"duration": 136
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 42828.70000000298,
|
|
||||||
"duration": 132
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1482.2000000029802,
|
|
||||||
"duration": 129
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frameCount": 460,
|
|
||||||
"averageFrameMs": 125.46608695652174,
|
|
||||||
"p95FrameMs": 233.39999999999782,
|
|
||||||
"p99FrameMs": 433.3000000000011,
|
|
||||||
"intervalDriftCount": 78,
|
|
||||||
"averageIntervalDriftMs": 502.9153846153655,
|
|
||||||
"p95IntervalDriftMs": 1530.1999999955297,
|
|
||||||
"p99IntervalDriftMs": 2415.60000000149
|
|
||||||
},
|
|
||||||
"consoleMessages": [
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"text": "[socket] connecting to https://rover.otter.land",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63768,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63768
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 404 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 404 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to enter spectator mode Error: Spectator role required\n at lo.<anonymous> (https://rover.otter.land/assets/index-l3JqdlyF.js:12:64487)\n at lo.onack (https://rover.otter.land/assets/index-l3JqdlyF.js:12:55937)\n at lo.onpacket (https://rover.otter.land/assets/index-l3JqdlyF.js:12:55148)\n at V2.emit (https://rover.otter.land/assets/index-l3JqdlyF.js:12:25579)\n at https://rover.otter.land/assets/index-l3JqdlyF.js:12:61163",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"line": 125,
|
|
||||||
"column": 14862,
|
|
||||||
"lineNumber": 125,
|
|
||||||
"columnNumber": 14862
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to enter spectator mode Error: Spectator role required\n at lo.<anonymous> (https://rover.otter.land/assets/index-l3JqdlyF.js:12:64487)\n at lo.onack (https://rover.otter.land/assets/index-l3JqdlyF.js:12:55937)\n at lo.onpacket (https://rover.otter.land/assets/index-l3JqdlyF.js:12:55148)\n at V2.emit (https://rover.otter.land/assets/index-l3JqdlyF.js:12:25579)\n at https://rover.otter.land/assets/index-l3JqdlyF.js:12:61163",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"line": 125,
|
|
||||||
"column": 14862,
|
|
||||||
"lineNumber": 125,
|
|
||||||
"columnNumber": 14862
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 404 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to enter spectator mode Error: Spectator role required\n at lo.<anonymous> (https://rover.otter.land/assets/index-l3JqdlyF.js:12:64487)\n at lo.onack (https://rover.otter.land/assets/index-l3JqdlyF.js:12:55937)\n at lo.onpacket (https://rover.otter.land/assets/index-l3JqdlyF.js:12:55148)\n at V2.emit (https://rover.otter.land/assets/index-l3JqdlyF.js:12:25579)\n at https://rover.otter.land/assets/index-l3JqdlyF.js:12:61163",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"line": 125,
|
|
||||||
"column": 14862,
|
|
||||||
"lineNumber": 125,
|
|
||||||
"columnNumber": 14862
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to enter spectator mode Error: Spectator role required\n at lo.<anonymous> (https://rover.otter.land/assets/index-l3JqdlyF.js:12:64487)\n at lo.onack (https://rover.otter.land/assets/index-l3JqdlyF.js:12:55937)\n at lo.onpacket (https://rover.otter.land/assets/index-l3JqdlyF.js:12:55148)\n at V2.emit (https://rover.otter.land/assets/index-l3JqdlyF.js:12:25579)\n at https://rover.otter.land/assets/index-l3JqdlyF.js:12:61163",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"line": 125,
|
|
||||||
"column": 14862,
|
|
||||||
"lineNumber": 125,
|
|
||||||
"columnNumber": 14862
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"failedRequests": [],
|
|
||||||
"errorResponses": [
|
|
||||||
{
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"status": 404
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"status": 404
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"status": 404
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 221 KiB |
@@ -1,483 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "https://rover.otter.land/",
|
|
||||||
"runDir": "perf/results/2026-06-11T03-15-51-398Z",
|
|
||||||
"elapsedMs": 68429,
|
|
||||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36",
|
|
||||||
"title": "Roomba Rover",
|
|
||||||
"finalUrl": "https://rover.otter.land/",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 8,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"afterLoad": {
|
|
||||||
"Timestamp": 39459.436441,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 964,
|
|
||||||
"Nodes": 2332,
|
|
||||||
"LayoutCount": 17,
|
|
||||||
"RecalcStyleCount": 19,
|
|
||||||
"LayoutDuration": 0.26351,
|
|
||||||
"RecalcStyleDuration": 0.176918,
|
|
||||||
"ScriptDuration": 3.214383,
|
|
||||||
"TaskDuration": 5.084233,
|
|
||||||
"JSHeapUsedSize": 8964616,
|
|
||||||
"JSHeapTotalSize": 20971520
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"Timestamp": 39514.079576,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 14445,
|
|
||||||
"Nodes": 2446,
|
|
||||||
"LayoutCount": 390,
|
|
||||||
"RecalcStyleCount": 565,
|
|
||||||
"LayoutDuration": 1.338259,
|
|
||||||
"RecalcStyleDuration": 1.340215,
|
|
||||||
"ScriptDuration": 39.06709,
|
|
||||||
"TaskDuration": 57.967635,
|
|
||||||
"JSHeapUsedSize": 32831868,
|
|
||||||
"JSHeapTotalSize": 80576512
|
|
||||||
},
|
|
||||||
"delta": {
|
|
||||||
"Timestamp": 54.64313499999844,
|
|
||||||
"Documents": 0,
|
|
||||||
"Frames": 0,
|
|
||||||
"JSEventListeners": 13481,
|
|
||||||
"Nodes": 114,
|
|
||||||
"LayoutCount": 373,
|
|
||||||
"RecalcStyleCount": 546,
|
|
||||||
"LayoutDuration": 1.0747490000000002,
|
|
||||||
"RecalcStyleDuration": 1.163297,
|
|
||||||
"ScriptDuration": 35.852707,
|
|
||||||
"TaskDuration": 52.883402000000004,
|
|
||||||
"JSHeapUsedSize": 23867252,
|
|
||||||
"JSHeapTotalSize": 59604992
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cpuSummary": {
|
|
||||||
"totalProfileMs": 42872.299,
|
|
||||||
"sampleCount": 32614,
|
|
||||||
"topSelfTime": [
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(program)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 11770.136000000031,
|
|
||||||
"samples": 8654
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "C9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 94304,
|
|
||||||
"selfMs": 2390.596999999998,
|
|
||||||
"samples": 1844
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "ad",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 7898,
|
|
||||||
"selfMs": 2379.0989999999983,
|
|
||||||
"samples": 1867
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "(anonymous)",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 129074,
|
|
||||||
"selfMs": 1738.0870000000016,
|
|
||||||
"samples": 1347
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Fl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 101199,
|
|
||||||
"selfMs": 1436.2390000000005,
|
|
||||||
"samples": 1104
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "removeEventListener",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 1298.9879999999996,
|
|
||||||
"samples": 915
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(idle)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 1226.9839999999986,
|
|
||||||
"samples": 919
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "v9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 71024,
|
|
||||||
"selfMs": 1131.7309999999986,
|
|
||||||
"samples": 880
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Q",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 39681,
|
|
||||||
"selfMs": 1085.1430000000012,
|
|
||||||
"samples": 833
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "va",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 10653,
|
|
||||||
"selfMs": 1036.2670000000012,
|
|
||||||
"samples": 808
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "w1",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 40723,
|
|
||||||
"selfMs": 1021.851000000001,
|
|
||||||
"samples": 794
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "addEventListener",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 935.4199999999993,
|
|
||||||
"samples": 733
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "zl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 80827,
|
|
||||||
"selfMs": 898.5989999999993,
|
|
||||||
"samples": 684
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Eh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 95724,
|
|
||||||
"selfMs": 875.6580000000007,
|
|
||||||
"samples": 685
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Al",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 97450,
|
|
||||||
"selfMs": 826.9259999999988,
|
|
||||||
"samples": 630
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Kl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 117211,
|
|
||||||
"selfMs": 710.8159999999998,
|
|
||||||
"samples": 538
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "n2",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 42234,
|
|
||||||
"selfMs": 586.3330000000001,
|
|
||||||
"samples": 449
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "kc",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 1388916,
|
|
||||||
"selfMs": 431.253,
|
|
||||||
"samples": 337
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Z4",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 28258,
|
|
||||||
"selfMs": 429.435,
|
|
||||||
"samples": 323
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "o1",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 70367,
|
|
||||||
"selfMs": 426.5330000000003,
|
|
||||||
"samples": 321
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "Oh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 116246,
|
|
||||||
"selfMs": 376.3850000000001,
|
|
||||||
"samples": 289
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "decodeString",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 48609,
|
|
||||||
"selfMs": 349.87800000000004,
|
|
||||||
"samples": 266
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "B6",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 34145,
|
|
||||||
"selfMs": 310.59499999999986,
|
|
||||||
"samples": 239
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "r",
|
|
||||||
"lineNumber": 1,
|
|
||||||
"columnNumber": 967,
|
|
||||||
"selfMs": 293.97700000000003,
|
|
||||||
"samples": 230
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(garbage collector)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 260.9860000000001,
|
|
||||||
"samples": 178
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "setAttribute",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 234.46500000000006,
|
|
||||||
"samples": 165
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "n9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 56257,
|
|
||||||
"selfMs": 221.65700000000007,
|
|
||||||
"samples": 163
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "t9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 50605,
|
|
||||||
"selfMs": 209.57700000000006,
|
|
||||||
"samples": 175
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "ma",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 10401,
|
|
||||||
"selfMs": 205.84599999999998,
|
|
||||||
"samples": 161
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"functionName": "w4",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 91309,
|
|
||||||
"selfMs": 181.84999999999997,
|
|
||||||
"samples": 140
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"responsiveness": {
|
|
||||||
"longTaskCount": 67,
|
|
||||||
"topLongTasks": [
|
|
||||||
{
|
|
||||||
"name": "unknown",
|
|
||||||
"startTime": 12851.70000000298,
|
|
||||||
"duration": 419
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 224.70000000298023,
|
|
||||||
"duration": 369
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1742.9000000059605,
|
|
||||||
"duration": 229
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 601.4000000059605,
|
|
||||||
"duration": 205
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 3460.2000000029802,
|
|
||||||
"duration": 203
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 36108.80000000447,
|
|
||||||
"duration": 202
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 3289.8000000044703,
|
|
||||||
"duration": 170
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1209.8000000044703,
|
|
||||||
"duration": 143
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1605.5,
|
|
||||||
"duration": 137
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 1353.3000000044703,
|
|
||||||
"duration": 123
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 4036.60000000149,
|
|
||||||
"duration": 110
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 31583.70000000298,
|
|
||||||
"duration": 107
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 807.1000000014901,
|
|
||||||
"duration": 106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "unknown",
|
|
||||||
"startTime": 13343,
|
|
||||||
"duration": 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 24534.20000000298,
|
|
||||||
"duration": 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 30488.30000000447,
|
|
||||||
"duration": 97
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 21806.40000000596,
|
|
||||||
"duration": 89
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 55545.5,
|
|
||||||
"duration": 87
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 36721.70000000298,
|
|
||||||
"duration": 82
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 44980.20000000298,
|
|
||||||
"duration": 79
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frameCount": 427,
|
|
||||||
"averageFrameMs": 140.080093676815,
|
|
||||||
"p95FrameMs": 166.79999999999563,
|
|
||||||
"p99FrameMs": 383.4000000000001,
|
|
||||||
"intervalDriftCount": 58,
|
|
||||||
"averageIntervalDriftMs": 790.1465517241379,
|
|
||||||
"p95IntervalDriftMs": 1347.1999999955297,
|
|
||||||
"p99IntervalDriftMs": 1620.7999999970198
|
|
||||||
},
|
|
||||||
"consoleMessages": [
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"text": "[socket] connecting to https://rover.otter.land",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/assets/index-l3JqdlyF.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63768,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63768
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 404 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 404 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 404 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"failedRequests": [],
|
|
||||||
"errorResponses": [
|
|
||||||
{
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"status": 404
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"status": 404
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"status": 404
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,581 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "https://rover.otter.land/",
|
|
||||||
"runDir": "perf/results/2026-06-11T03-21-51-263Z-root-runtime",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 6,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true,
|
|
||||||
"sampleMs": 30000
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"Timestamp": 39844.241947,
|
|
||||||
"AudioHandlers": 0,
|
|
||||||
"AudioWorkletProcessors": 0,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 15871,
|
|
||||||
"LayoutObjects": 1938,
|
|
||||||
"MediaKeySessions": 0,
|
|
||||||
"MediaKeys": 0,
|
|
||||||
"Nodes": 2430,
|
|
||||||
"Resources": 13,
|
|
||||||
"ContextLifecycleStateObservers": 12,
|
|
||||||
"V8PerContextDatas": 2,
|
|
||||||
"WorkerGlobalScopes": 0,
|
|
||||||
"UACSSResources": 0,
|
|
||||||
"RTCPeerConnections": 2,
|
|
||||||
"ResourceFetchers": 1,
|
|
||||||
"AdSubframes": 0,
|
|
||||||
"DetachedScriptStates": 2,
|
|
||||||
"ArrayBufferContents": 3,
|
|
||||||
"LayoutCount": 297,
|
|
||||||
"RecalcStyleCount": 320,
|
|
||||||
"LayoutDuration": 0.596739,
|
|
||||||
"RecalcStyleDuration": 0.63084,
|
|
||||||
"DevToolsCommandDuration": 0.000137,
|
|
||||||
"ScriptDuration": 20.110942,
|
|
||||||
"V8CompileDuration": 0,
|
|
||||||
"TaskDuration": 29.506741,
|
|
||||||
"TaskOtherDuration": 8.168083,
|
|
||||||
"ThreadTime": 30.245205,
|
|
||||||
"ProcessTime": 35.2,
|
|
||||||
"JSHeapUsedSize": 18751188,
|
|
||||||
"JSHeapTotalSize": 45088768,
|
|
||||||
"FirstMeaningfulPaint": 0,
|
|
||||||
"DomContentLoaded": 39814.213839,
|
|
||||||
"NavigationStart": 39813.701422
|
|
||||||
},
|
|
||||||
"snapshot": {
|
|
||||||
"elapsedMs": 30586.79999999702,
|
|
||||||
"listeners": [
|
|
||||||
{
|
|
||||||
"key": "add:window:keydown",
|
|
||||||
"count": 770
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:keyup",
|
|
||||||
"count": 770
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:blur",
|
|
||||||
"count": 770
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:gamepadconnected",
|
|
||||||
"count": 770
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:gamepaddisconnected",
|
|
||||||
"count": 770
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:keydown",
|
|
||||||
"count": 769
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:keyup",
|
|
||||||
"count": 769
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:blur",
|
|
||||||
"count": 769
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:gamepadconnected",
|
|
||||||
"count": 769
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:gamepaddisconnected",
|
|
||||||
"count": 769
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:input:invalid",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:online",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:document:visibilitychange",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:online",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:document:visibilitychange",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:canplay",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:ended",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:error",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:pause",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:play",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:stalled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:waiting",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:auxclick",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:click",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:contextmenu",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:copy",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:cut",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drag",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragend",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragenter",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragexit",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragleave",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragover",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragstart",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drop",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:gotpointercapture",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:input",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keydown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keypress",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keyup",
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timers": [
|
|
||||||
{
|
|
||||||
"key": "interval:200",
|
|
||||||
"count": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:12000",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:0",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:250",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:3000",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:60000",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:15000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:1000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:2000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:4000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:10000",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeouts": 13,
|
|
||||||
"intervals": 25,
|
|
||||||
"timeoutFires": 6,
|
|
||||||
"intervalFires": 593,
|
|
||||||
"webSocketMessageCount": 1000,
|
|
||||||
"webSocketBytes": 797561,
|
|
||||||
"webSocketEvents": [
|
|
||||||
{
|
|
||||||
"event": "log:entry",
|
|
||||||
"count": 526,
|
|
||||||
"bytes": 111535
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "sensorFrame",
|
|
||||||
"count": 306,
|
|
||||||
"bytes": 574616
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "commandAck",
|
|
||||||
"count": 145,
|
|
||||||
"bytes": 13778
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "chat:typing",
|
|
||||||
"count": 7,
|
|
||||||
"bytes": 3073
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "session:sync",
|
|
||||||
"count": 5,
|
|
||||||
"bytes": 87372
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "chat:message",
|
|
||||||
"count": 4,
|
|
||||||
"bytes": 4115
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverHostStats",
|
|
||||||
"count": 4,
|
|
||||||
"bytes": 3069
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "2",
|
|
||||||
"count": 3,
|
|
||||||
"bytes": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationsTotal": {
|
|
||||||
"records": 41062,
|
|
||||||
"added": 229,
|
|
||||||
"removed": 35,
|
|
||||||
"attrs": 40761,
|
|
||||||
"text": 37
|
|
||||||
},
|
|
||||||
"mutationBursts": [
|
|
||||||
{
|
|
||||||
"at": 1534,
|
|
||||||
"records": 164,
|
|
||||||
"added": 105,
|
|
||||||
"removed": 1,
|
|
||||||
"attrs": 58,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 3007,
|
|
||||||
"records": 93,
|
|
||||||
"added": 13,
|
|
||||||
"removed": 3,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 1212,
|
|
||||||
"records": 73,
|
|
||||||
"added": 22,
|
|
||||||
"removed": 5,
|
|
||||||
"attrs": 42,
|
|
||||||
"text": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 6080,
|
|
||||||
"records": 72,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 72,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 7837,
|
|
||||||
"records": 72,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 72,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 8493,
|
|
||||||
"records": 72,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 72,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 16038,
|
|
||||||
"records": 72,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 72,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26549,
|
|
||||||
"records": 72,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 72,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 10855,
|
|
||||||
"records": 71,
|
|
||||||
"added": 1,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 70,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 4339,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 6195,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 6225,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 9779,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15605,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15797,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15862,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 18303,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 18367,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 20816,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 20907,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frames": {
|
|
||||||
"count": 338,
|
|
||||||
"average": 88.21153846153847,
|
|
||||||
"p95": 150,
|
|
||||||
"p99": 183.29999999999927
|
|
||||||
},
|
|
||||||
"longTasks": {
|
|
||||||
"count": 18,
|
|
||||||
"top": [
|
|
||||||
{
|
|
||||||
"startTime": 228,
|
|
||||||
"duration": 287,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 520.5,
|
|
||||||
"duration": 226,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1534.1000000014901,
|
|
||||||
"duration": 140,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 2490,
|
|
||||||
"duration": 136,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1080,
|
|
||||||
"duration": 132,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1212.3000000044703,
|
|
||||||
"duration": 124,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1434.8999999985099,
|
|
||||||
"duration": 99,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 747.1000000014901,
|
|
||||||
"duration": 85,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 2928.60000000149,
|
|
||||||
"duration": 78,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 11610.30000000447,
|
|
||||||
"duration": 72,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 4531.10000000149,
|
|
||||||
"duration": 62,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 19226.5,
|
|
||||||
"duration": 62,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 6920.70000000298,
|
|
||||||
"duration": 54,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 7968.39999999851,
|
|
||||||
"duration": 52,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3745.60000000149,
|
|
||||||
"duration": 51,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 12822.20000000298,
|
|
||||||
"duration": 51,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 8143.70000000298,
|
|
||||||
"duration": 50,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 10881.5,
|
|
||||||
"duration": 50,
|
|
||||||
"name": "self"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"consoleErrors": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 923 KiB |
@@ -1,843 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "https://rover.otter.land/",
|
|
||||||
"runDir": "perf/results/2026-06-11T03-22-44-714Z-root-runtime",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 6,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true,
|
|
||||||
"sampleMs": 18000
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"Timestamp": 39885.73698,
|
|
||||||
"AudioHandlers": 0,
|
|
||||||
"AudioWorkletProcessors": 0,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 8530,
|
|
||||||
"LayoutObjects": 1864,
|
|
||||||
"MediaKeySessions": 0,
|
|
||||||
"MediaKeys": 0,
|
|
||||||
"Nodes": 2379,
|
|
||||||
"Resources": 12,
|
|
||||||
"ContextLifecycleStateObservers": 12,
|
|
||||||
"V8PerContextDatas": 2,
|
|
||||||
"WorkerGlobalScopes": 0,
|
|
||||||
"UACSSResources": 0,
|
|
||||||
"RTCPeerConnections": 2,
|
|
||||||
"ResourceFetchers": 1,
|
|
||||||
"AdSubframes": 0,
|
|
||||||
"DetachedScriptStates": 2,
|
|
||||||
"ArrayBufferContents": 8,
|
|
||||||
"LayoutCount": 116,
|
|
||||||
"RecalcStyleCount": 120,
|
|
||||||
"LayoutDuration": 0.379961,
|
|
||||||
"RecalcStyleDuration": 0.361646,
|
|
||||||
"DevToolsCommandDuration": 0.000126,
|
|
||||||
"ScriptDuration": 11.946269,
|
|
||||||
"V8CompileDuration": 0,
|
|
||||||
"TaskDuration": 17.591645,
|
|
||||||
"TaskOtherDuration": 4.903643,
|
|
||||||
"ThreadTime": 18.399532,
|
|
||||||
"ProcessTime": 20.97,
|
|
||||||
"JSHeapUsedSize": 24567940,
|
|
||||||
"JSHeapTotalSize": 44564480,
|
|
||||||
"FirstMeaningfulPaint": 0,
|
|
||||||
"DomContentLoaded": 39867.72907,
|
|
||||||
"NavigationStart": 39867.192605
|
|
||||||
},
|
|
||||||
"snapshot": {
|
|
||||||
"elapsedMs": 18737.20000000298,
|
|
||||||
"listeners": [
|
|
||||||
{
|
|
||||||
"key": "add:window:keydown",
|
|
||||||
"count": 334
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:keyup",
|
|
||||||
"count": 334
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:blur",
|
|
||||||
"count": 334
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:gamepadconnected",
|
|
||||||
"count": 334
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:gamepaddisconnected",
|
|
||||||
"count": 334
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:keydown",
|
|
||||||
"count": 333
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:keyup",
|
|
||||||
"count": 333
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:blur",
|
|
||||||
"count": 333
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:gamepadconnected",
|
|
||||||
"count": 333
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:gamepaddisconnected",
|
|
||||||
"count": 333
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:input:invalid",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:online",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:document:visibilitychange",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:online",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:document:visibilitychange",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:canplay",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:ended",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:error",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:pause",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:play",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:stalled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:waiting",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:auxclick",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:click",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:contextmenu",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:copy",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:cut",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drag",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragend",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragenter",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragexit",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragleave",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragover",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragstart",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drop",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:gotpointercapture",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:input",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keydown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keypress",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keyup",
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timers": [
|
|
||||||
{
|
|
||||||
"key": "interval:200",
|
|
||||||
"count": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:0",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:12000",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:60000",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:250",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:3000",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:1000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:15000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:2000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:4000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:10000",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeouts": 11,
|
|
||||||
"intervals": 22,
|
|
||||||
"timeoutFires": 6,
|
|
||||||
"intervalFires": 148,
|
|
||||||
"webSocketMessageCount": 1000,
|
|
||||||
"webSocketBytes": 762620,
|
|
||||||
"webSocketEvents": [
|
|
||||||
{
|
|
||||||
"event": "log:entry",
|
|
||||||
"count": 850,
|
|
||||||
"bytes": 185008
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "sensorFrame",
|
|
||||||
"count": 277,
|
|
||||||
"bytes": 520169
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "commandAck",
|
|
||||||
"count": 200,
|
|
||||||
"bytes": 19180
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "session:sync",
|
|
||||||
"count": 23,
|
|
||||||
"bytes": 401624
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "",
|
|
||||||
"count": 5,
|
|
||||||
"bytes": 5008402
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "chat:typing",
|
|
||||||
"count": 4,
|
|
||||||
"bytes": 1748
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverSnapshot:frame",
|
|
||||||
"count": 3,
|
|
||||||
"bytes": 291
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "2",
|
|
||||||
"count": 3,
|
|
||||||
"bytes": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverHostStats",
|
|
||||||
"count": 3,
|
|
||||||
"bytes": 2306
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "alert",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 256
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "alert:new",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 329
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "0{\"sid\":\"pSJ",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "40{\"sid\":\"3t",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "mode",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 27
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "controlGranted",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "auth:role",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 31
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "rovers",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 5485
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "chat:init",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 91696
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "overseer:memory",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 1505
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "log:init",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 41011
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "barcode:state",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 1619
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "kinect:pointCloudFrame",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 262
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "kinect:colorFrame",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 169
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "430[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 42
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "431[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 133
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "432[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 145
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverSnapshot:status",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 79
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "Freaky",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "434[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 194
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "435[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 194
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationsTotal": {
|
|
||||||
"records": 18758,
|
|
||||||
"added": 215,
|
|
||||||
"removed": 17,
|
|
||||||
"attrs": 18490,
|
|
||||||
"text": 36
|
|
||||||
},
|
|
||||||
"mutationTargets": [
|
|
||||||
{
|
|
||||||
"key": "input.accent-emerald-400:attributes:name",
|
|
||||||
"count": 5300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-emerald-400:attributes:type",
|
|
||||||
"count": 2650
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:name",
|
|
||||||
"count": 1332
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.flex-1:attributes:name",
|
|
||||||
"count": 1328
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-cyan-500:attributes:name",
|
|
||||||
"count": 1324
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.h-full:attributes:style",
|
|
||||||
"count": 1238
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:name",
|
|
||||||
"count": 666
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.chat-composer-input:attributes:name",
|
|
||||||
"count": 666
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input:attributes:name",
|
|
||||||
"count": 666
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:type",
|
|
||||||
"count": 666
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-cyan-500:attributes:type",
|
|
||||||
"count": 662
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:d",
|
|
||||||
"count": 621
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:fill",
|
|
||||||
"count": 619
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:type",
|
|
||||||
"count": 333
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input:attributes:type",
|
|
||||||
"count": 333
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "Text:characterData:",
|
|
||||||
"count": 36
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.min-w-0.flex-1:attributes:name",
|
|
||||||
"count": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "section.panel-section.border:attributes:style",
|
|
||||||
"count": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "header.flex.items-center:attributes:style",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:stroke",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "span.inline-flex.items-center:attributes:class",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.flex:attributes:disabled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.min-h-9:attributes:disabled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:value",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.flex-1:attributes:value",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.flex.flex-col:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:placeholder",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.rounded-sm.px-2:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.button-dark.min-w-0:attributes:disabled",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.flex:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.mobile-touch-control.flex:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:value",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.button-dark.text-sm:attributes:disabled",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:playsinline",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:autoplay",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:disableremoteplayback",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "audio:attributes:autoplay",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "audio:attributes:disableremoteplayback",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.fixed.bottom-2:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "span.rounded.px-1:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationBursts": [
|
|
||||||
{
|
|
||||||
"at": 1633,
|
|
||||||
"records": 164,
|
|
||||||
"added": 105,
|
|
||||||
"removed": 1,
|
|
||||||
"attrs": 58,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 3468,
|
|
||||||
"records": 79,
|
|
||||||
"added": 11,
|
|
||||||
"removed": 2,
|
|
||||||
"attrs": 64,
|
|
||||||
"text": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 1295,
|
|
||||||
"records": 73,
|
|
||||||
"added": 22,
|
|
||||||
"removed": 5,
|
|
||||||
"attrs": 42,
|
|
||||||
"text": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 5655,
|
|
||||||
"records": 72,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 72,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 5790,
|
|
||||||
"records": 72,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 72,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 16256,
|
|
||||||
"records": 72,
|
|
||||||
"added": 3,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 67,
|
|
||||||
"text": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 6959,
|
|
||||||
"records": 70,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 69,
|
|
||||||
"text": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 4549,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 4590,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 5282,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 5711,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 6485,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 7777,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 8498,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 8545,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 9500,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 9569,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 11021,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 11795,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 17038,
|
|
||||||
"records": 68,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 68,
|
|
||||||
"text": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frames": {
|
|
||||||
"count": 153,
|
|
||||||
"average": 116.5535947712418,
|
|
||||||
"p95": 166.69999999999982,
|
|
||||||
"p99": 316.60000000000014
|
|
||||||
},
|
|
||||||
"longTasks": {
|
|
||||||
"count": 16,
|
|
||||||
"top": [
|
|
||||||
{
|
|
||||||
"startTime": 238.79999999701977,
|
|
||||||
"duration": 299,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 542.8999999985099,
|
|
||||||
"duration": 192,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3064.10000000149,
|
|
||||||
"duration": 177,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1632.8999999985099,
|
|
||||||
"duration": 170,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1154.8999999985099,
|
|
||||||
"duration": 140,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 2930.2999999970198,
|
|
||||||
"duration": 133,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1505.6999999955297,
|
|
||||||
"duration": 127,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 735.6999999955297,
|
|
||||||
"duration": 117,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1295.8999999985099,
|
|
||||||
"duration": 108,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3674.89999999851,
|
|
||||||
"duration": 83,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3387,
|
|
||||||
"duration": 81,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3567.89999999851,
|
|
||||||
"duration": 59,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1909,
|
|
||||||
"duration": 57,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 7985.10000000149,
|
|
||||||
"duration": 55,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 12968.5,
|
|
||||||
"duration": 55,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 931.6999999955297,
|
|
||||||
"duration": 53,
|
|
||||||
"name": "self"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"consoleErrors": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 875 KiB |
@@ -1,858 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "https://rover.otter.land/spectate",
|
|
||||||
"runDir": "perf/results/2026-06-12T04-55-16-888Z-root-runtime",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 6,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true,
|
|
||||||
"sampleMs": 30000
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"Timestamp": 29302.703615,
|
|
||||||
"AudioHandlers": 0,
|
|
||||||
"AudioWorkletProcessors": 0,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 379,
|
|
||||||
"LayoutObjects": 6150,
|
|
||||||
"MediaKeySessions": 0,
|
|
||||||
"MediaKeys": 0,
|
|
||||||
"Nodes": 7059,
|
|
||||||
"Resources": 40,
|
|
||||||
"ContextLifecycleStateObservers": 21,
|
|
||||||
"V8PerContextDatas": 2,
|
|
||||||
"WorkerGlobalScopes": 0,
|
|
||||||
"UACSSResources": 0,
|
|
||||||
"RTCPeerConnections": 6,
|
|
||||||
"ResourceFetchers": 1,
|
|
||||||
"AdSubframes": 0,
|
|
||||||
"DetachedScriptStates": 0,
|
|
||||||
"ArrayBufferContents": 28,
|
|
||||||
"LayoutCount": 243,
|
|
||||||
"RecalcStyleCount": 248,
|
|
||||||
"LayoutDuration": 0.840302,
|
|
||||||
"RecalcStyleDuration": 1.699961,
|
|
||||||
"DevToolsCommandDuration": 0.000155,
|
|
||||||
"ScriptDuration": 12.114687,
|
|
||||||
"V8CompileDuration": 0,
|
|
||||||
"TaskDuration": 28.48339,
|
|
||||||
"TaskOtherDuration": 13.828285,
|
|
||||||
"ThreadTime": 30.190528,
|
|
||||||
"ProcessTime": 39.47,
|
|
||||||
"JSHeapUsedSize": 22447504,
|
|
||||||
"JSHeapTotalSize": 42467328,
|
|
||||||
"FirstMeaningfulPaint": 0,
|
|
||||||
"DomContentLoaded": 29272.696156,
|
|
||||||
"NavigationStart": 29272.010802
|
|
||||||
},
|
|
||||||
"snapshot": {
|
|
||||||
"elapsedMs": 30914.699999999255,
|
|
||||||
"listeners": [
|
|
||||||
{
|
|
||||||
"key": "add:audio:canplay",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:ended",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:error",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:pause",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:play",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:stalled",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:waiting",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:online",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:document:visibilitychange",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:img:error",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:img:load",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:input:invalid",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:online",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:document:visibilitychange",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:abort",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:canplaythrough",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:durationchange",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:emptied",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:encrypted",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:loadeddata",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:loadedmetadata",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:loadstart",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:playing",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:progress",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:ratechange",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:resize",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:seeked",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:seeking",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:suspend",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:timeupdate",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:volumechange",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:video:abort",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:video:canplay",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:video:canplaythrough",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:video:durationchange",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:video:emptied",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:video:encrypted",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:video:ended",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:video:error",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:video:loadeddata",
|
|
||||||
"count": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timers": [
|
|
||||||
{
|
|
||||||
"key": "interval:3000",
|
|
||||||
"count": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:12000",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:1200",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:60000",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:0",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:200",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:15000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:2000",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeouts": 14,
|
|
||||||
"intervals": 18,
|
|
||||||
"timeoutFires": 7,
|
|
||||||
"intervalFires": 112,
|
|
||||||
"webSocketMessageCount": 1000,
|
|
||||||
"webSocketBytes": 4207474,
|
|
||||||
"webSocketEvents": [
|
|
||||||
{
|
|
||||||
"event": "sensorFrame",
|
|
||||||
"count": 1354,
|
|
||||||
"bytes": 2531090
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "log:entry",
|
|
||||||
"count": 634,
|
|
||||||
"bytes": 150179
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "",
|
|
||||||
"count": 126,
|
|
||||||
"bytes": 11048477
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roomCamera:frame",
|
|
||||||
"count": 117,
|
|
||||||
"bytes": 10413
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "commandAck",
|
|
||||||
"count": 68,
|
|
||||||
"bytes": 6412
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "session:sync",
|
|
||||||
"count": 54,
|
|
||||||
"bytes": 914000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverHostStats",
|
|
||||||
"count": 17,
|
|
||||||
"bytes": 13058
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverSnapshot:frame",
|
|
||||||
"count": 7,
|
|
||||||
"bytes": 682
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "2",
|
|
||||||
"count": 5,
|
|
||||||
"bytes": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "auth:role",
|
|
||||||
"count": 4,
|
|
||||||
"bytes": 139
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roomCamera:status",
|
|
||||||
"count": 4,
|
|
||||||
"bytes": 284
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverSnapshot:status",
|
|
||||||
"count": 3,
|
|
||||||
"bytes": 238
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "alert",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 256
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "alert:new",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 330
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "0{\"sid\":\"3a5",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "40{\"sid\":\"lE",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "mode",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 27
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "controlGranted",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "rovers",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 5485
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "chat:init",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 77415
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "overseer:memory",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 520
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "log:init",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 44530
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "barcode:state",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 1627
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "kinect:pointCloudFrame",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 262
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "kinect:colorFrame",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 169
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "430[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "431[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "432[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 194
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "433[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "434[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 133
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "435[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 145
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "WALL-E",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "437[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 133
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "438[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 145
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "Freaky",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "4310[{\"url\":",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 136
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "4311[{\"url\":",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 148
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "Bweeble",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 42
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "carpet",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 68
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "4314[{\"succe",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "4315[{\"succe",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "4316[{\"succe",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "4317[{\"succe",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 195
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "4318[{\"succe",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "4319[{\"succe",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 22
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationsTotal": {
|
|
||||||
"records": 5868,
|
|
||||||
"added": 440,
|
|
||||||
"removed": 440,
|
|
||||||
"attrs": 4921,
|
|
||||||
"text": 67
|
|
||||||
},
|
|
||||||
"mutationTargets": [
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:d",
|
|
||||||
"count": 2688
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:fill",
|
|
||||||
"count": 2682
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "Text:characterData:",
|
|
||||||
"count": 164
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "img.h-full.w-full:attributes:src",
|
|
||||||
"count": 117
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "span.h-2.w-2:attributes:class",
|
|
||||||
"count": 117
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.flex-1:attributes:name",
|
|
||||||
"count": 102
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.chat-composer-input:attributes:name",
|
|
||||||
"count": 102
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:stroke",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "section.panel-section.border:attributes:style",
|
|
||||||
"count": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "span.rounded.px-1.5:attributes:class",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "header.flex.items-center:attributes:style",
|
|
||||||
"count": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "article.surface.px-0.5:attributes:class",
|
|
||||||
"count": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "li.surface.flex:attributes:class",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:playsinline",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:autoplay",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:disableremoteplayback",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "audio:attributes:autoplay",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "audio:attributes:disableremoteplayback",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-cyan-500:attributes:name",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.panel-section.flex:attributes:style",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "span.rounded.px-1:attributes:class",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-cyan-500:attributes:type",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.flex-1:attributes:value",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationBursts": [
|
|
||||||
{
|
|
||||||
"at": 13575,
|
|
||||||
"records": 119,
|
|
||||||
"added": 57,
|
|
||||||
"removed": 58,
|
|
||||||
"attrs": 4,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 18089,
|
|
||||||
"records": 117,
|
|
||||||
"added": 52,
|
|
||||||
"removed": 53,
|
|
||||||
"attrs": 12,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 27878,
|
|
||||||
"records": 113,
|
|
||||||
"added": 52,
|
|
||||||
"removed": 52,
|
|
||||||
"attrs": 8,
|
|
||||||
"text": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 22882,
|
|
||||||
"records": 102,
|
|
||||||
"added": 49,
|
|
||||||
"removed": 49,
|
|
||||||
"attrs": 4,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 9538,
|
|
||||||
"records": 100,
|
|
||||||
"added": 48,
|
|
||||||
"removed": 48,
|
|
||||||
"attrs": 4,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 14782,
|
|
||||||
"records": 84,
|
|
||||||
"added": 37,
|
|
||||||
"removed": 35,
|
|
||||||
"attrs": 12,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15222,
|
|
||||||
"records": 83,
|
|
||||||
"added": 39,
|
|
||||||
"removed": 39,
|
|
||||||
"attrs": 5,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 9811,
|
|
||||||
"records": 68,
|
|
||||||
"added": 28,
|
|
||||||
"removed": 28,
|
|
||||||
"attrs": 12,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 13733,
|
|
||||||
"records": 36,
|
|
||||||
"added": 13,
|
|
||||||
"removed": 13,
|
|
||||||
"attrs": 10,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 19439,
|
|
||||||
"records": 26,
|
|
||||||
"added": 10,
|
|
||||||
"removed": 10,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 23034,
|
|
||||||
"records": 18,
|
|
||||||
"added": 3,
|
|
||||||
"removed": 3,
|
|
||||||
"attrs": 12,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 23113,
|
|
||||||
"records": 18,
|
|
||||||
"added": 4,
|
|
||||||
"removed": 4,
|
|
||||||
"attrs": 10,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 28037,
|
|
||||||
"records": 18,
|
|
||||||
"added": 4,
|
|
||||||
"removed": 4,
|
|
||||||
"attrs": 10,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 10169,
|
|
||||||
"records": 16,
|
|
||||||
"added": 4,
|
|
||||||
"removed": 4,
|
|
||||||
"attrs": 8,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 18258,
|
|
||||||
"records": 16,
|
|
||||||
"added": 4,
|
|
||||||
"removed": 4,
|
|
||||||
"attrs": 8,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 13903,
|
|
||||||
"records": 15,
|
|
||||||
"added": 4,
|
|
||||||
"removed": 4,
|
|
||||||
"attrs": 7,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 14108,
|
|
||||||
"records": 15,
|
|
||||||
"added": 2,
|
|
||||||
"removed": 2,
|
|
||||||
"attrs": 10,
|
|
||||||
"text": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 16376,
|
|
||||||
"records": 15,
|
|
||||||
"added": 2,
|
|
||||||
"removed": 2,
|
|
||||||
"attrs": 11,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 11700,
|
|
||||||
"records": 14,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 14,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 13012,
|
|
||||||
"records": 14,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 13,
|
|
||||||
"text": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frames": {
|
|
||||||
"count": 264,
|
|
||||||
"average": 113.8840909090909,
|
|
||||||
"p95": 233.29999999999927,
|
|
||||||
"p99": 300
|
|
||||||
},
|
|
||||||
"longTasks": {
|
|
||||||
"count": 81,
|
|
||||||
"top": [
|
|
||||||
{
|
|
||||||
"startTime": 311.5999999977648,
|
|
||||||
"duration": 375,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1978.699999999255,
|
|
||||||
"duration": 299,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1254.5999999977648,
|
|
||||||
"duration": 229,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1772,
|
|
||||||
"duration": 206,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 1485.3999999985099,
|
|
||||||
"duration": 170,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 2646.800000000745,
|
|
||||||
"duration": 165,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 2507.5,
|
|
||||||
"duration": 139,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 693.8000000007451,
|
|
||||||
"duration": 125,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3026.800000000745,
|
|
||||||
"duration": 119,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 4148.39999999851,
|
|
||||||
"duration": 113,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3461.099999997765,
|
|
||||||
"duration": 109,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 9812.89999999851,
|
|
||||||
"duration": 106,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 14821.39999999851,
|
|
||||||
"duration": 100,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3145.800000000745,
|
|
||||||
"duration": 97,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 4447.39999999851,
|
|
||||||
"duration": 96,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 9551,
|
|
||||||
"duration": 96,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 9939.300000000745,
|
|
||||||
"duration": 95,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 18100.89999999851,
|
|
||||||
"duration": 92,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 23114.89999999851,
|
|
||||||
"duration": 92,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 18302.800000000745,
|
|
||||||
"duration": 88,
|
|
||||||
"name": "self"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"consoleErrors": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 6.3 MiB |
@@ -1,723 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/",
|
|
||||||
"runDir": "perf/results/2026-06-12T05-27-45-384Z-root-runtime",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 6,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true,
|
|
||||||
"sampleMs": 30000
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"Timestamp": 20904.330789,
|
|
||||||
"AudioHandlers": 0,
|
|
||||||
"AudioWorkletProcessors": 0,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 365,
|
|
||||||
"LayoutObjects": 1886,
|
|
||||||
"MediaKeySessions": 0,
|
|
||||||
"MediaKeys": 0,
|
|
||||||
"Nodes": 2224,
|
|
||||||
"Resources": 24,
|
|
||||||
"ContextLifecycleStateObservers": 14,
|
|
||||||
"V8PerContextDatas": 2,
|
|
||||||
"WorkerGlobalScopes": 0,
|
|
||||||
"UACSSResources": 0,
|
|
||||||
"RTCPeerConnections": 0,
|
|
||||||
"ResourceFetchers": 1,
|
|
||||||
"AdSubframes": 0,
|
|
||||||
"DetachedScriptStates": 2,
|
|
||||||
"ArrayBufferContents": 1,
|
|
||||||
"LayoutCount": 11,
|
|
||||||
"RecalcStyleCount": 12,
|
|
||||||
"LayoutDuration": 0.81696,
|
|
||||||
"RecalcStyleDuration": 0.7972,
|
|
||||||
"DevToolsCommandDuration": 0.000356,
|
|
||||||
"ScriptDuration": 26.612771,
|
|
||||||
"V8CompileDuration": 0,
|
|
||||||
"TaskDuration": 34.234417,
|
|
||||||
"TaskOtherDuration": 6.00713,
|
|
||||||
"ThreadTime": 35.26645,
|
|
||||||
"ProcessTime": 42.01,
|
|
||||||
"JSHeapUsedSize": 20964432,
|
|
||||||
"JSHeapTotalSize": 35799040,
|
|
||||||
"FirstMeaningfulPaint": 0,
|
|
||||||
"DomContentLoaded": 20874.278609,
|
|
||||||
"NavigationStart": 20868.27481
|
|
||||||
},
|
|
||||||
"snapshot": {
|
|
||||||
"elapsedMs": 37773.099999997765,
|
|
||||||
"listeners": [
|
|
||||||
{
|
|
||||||
"key": "add:input:invalid",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:online",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:document:visibilitychange",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:online",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:document:visibilitychange",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:WebSocket:message",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:document:fullscreenchange",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:document:webkitfullscreenchange",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:canplay",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:ended",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:error",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:pause",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:play",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:stalled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:waiting",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:auxclick",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:click",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:contextmenu",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:copy",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:cut",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drag",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragend",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragenter",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragexit",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragleave",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragover",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragstart",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drop",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:gotpointercapture",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:input",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keydown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keypress",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keyup",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:lostpointercapture",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mousedown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mousemove",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mouseout",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mouseover",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mouseup",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:paste",
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timers": [
|
|
||||||
{
|
|
||||||
"key": "timeout:0",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:250",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:200",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:15000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:1000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:2000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:12000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:4000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:60000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:30000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:10000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:592",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeouts": 14,
|
|
||||||
"intervals": 15,
|
|
||||||
"timeoutFires": 9,
|
|
||||||
"intervalFires": 46,
|
|
||||||
"webSocketMessageCount": 103,
|
|
||||||
"webSocketBytes": 512324,
|
|
||||||
"webSocketEvents": [
|
|
||||||
{
|
|
||||||
"event": "log:entry",
|
|
||||||
"count": 62,
|
|
||||||
"bytes": 14692
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "session:sync",
|
|
||||||
"count": 14,
|
|
||||||
"bytes": 237697
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "mode",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 54
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "controlGranted",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 82
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "alert",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 256
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "alert:new",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 330
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "auth:role",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 62
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "rovers",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 10972
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "chat:init",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 155226
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "overseer:memory",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 1040
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "log:init",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 87839
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "barcode:state",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 3254
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "kinect:pointCloudFrame",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 524
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "{\"type\":\"con",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "0{\"sid\":\"SuW",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "40{\"sid\":\"Wx",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "0{\"sid\":\"mxD",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "40{\"sid\":\"mW",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 32
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationsTotal": {
|
|
||||||
"records": 536,
|
|
||||||
"added": 170,
|
|
||||||
"removed": 11,
|
|
||||||
"attrs": 346,
|
|
||||||
"text": 9
|
|
||||||
},
|
|
||||||
"mutationTargets": [
|
|
||||||
{
|
|
||||||
"key": "input.accent-emerald-400:attributes:name",
|
|
||||||
"count": 102
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-emerald-400:attributes:type",
|
|
||||||
"count": 51
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:name",
|
|
||||||
"count": 28
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.flex-1:attributes:name",
|
|
||||||
"count": 24
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:name",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-cyan-500:attributes:name",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.chat-composer-input:attributes:name",
|
|
||||||
"count": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input:attributes:name",
|
|
||||||
"count": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:type",
|
|
||||||
"count": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "section.panel-section.border:attributes:style",
|
|
||||||
"count": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "Text:characterData:",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:type",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "header.flex.items-center:attributes:style",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-cyan-500:attributes:type",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input:attributes:type",
|
|
||||||
"count": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.rounded-sm.px-2:attributes:class",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.flex:attributes:disabled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.min-h-9:attributes:disabled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:value",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.fixed.bottom-2:attributes:class",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.min-w-0.flex-1:attributes:name",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.flex.flex-col:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:placeholder",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "span.inline-flex.items-center:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.button-dark.min-w-0:attributes:disabled",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationBursts": [
|
|
||||||
{
|
|
||||||
"at": 17875,
|
|
||||||
"records": 171,
|
|
||||||
"added": 117,
|
|
||||||
"removed": 6,
|
|
||||||
"attrs": 46,
|
|
||||||
"text": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 24975,
|
|
||||||
"records": 62,
|
|
||||||
"added": 5,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 57,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 37525,
|
|
||||||
"records": 56,
|
|
||||||
"added": 4,
|
|
||||||
"removed": 2,
|
|
||||||
"attrs": 50,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 29221,
|
|
||||||
"records": 54,
|
|
||||||
"added": 1,
|
|
||||||
"removed": 1,
|
|
||||||
"attrs": 50,
|
|
||||||
"text": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 33394,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 1,
|
|
||||||
"attrs": 49,
|
|
||||||
"text": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 29801,
|
|
||||||
"records": 28,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 27,
|
|
||||||
"text": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 30093,
|
|
||||||
"records": 28,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 1,
|
|
||||||
"attrs": 27,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 245,
|
|
||||||
"records": 27,
|
|
||||||
"added": 27,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 0,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 11721,
|
|
||||||
"records": 23,
|
|
||||||
"added": 2,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 20,
|
|
||||||
"text": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 10636,
|
|
||||||
"records": 20,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 20,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 218,
|
|
||||||
"records": 6,
|
|
||||||
"added": 6,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 0,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 272,
|
|
||||||
"records": 3,
|
|
||||||
"added": 3,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 0,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 227,
|
|
||||||
"records": 2,
|
|
||||||
"added": 2,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 0,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 352,
|
|
||||||
"records": 2,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 0,
|
|
||||||
"text": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 282,
|
|
||||||
"records": 1,
|
|
||||||
"added": 1,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 0,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 5981,
|
|
||||||
"records": 1,
|
|
||||||
"added": 1,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 0,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 8409,
|
|
||||||
"records": 1,
|
|
||||||
"added": 1,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 0,
|
|
||||||
"text": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frames": {
|
|
||||||
"count": 65,
|
|
||||||
"average": 571.7707692307692,
|
|
||||||
"p95": 3433.0999999999985,
|
|
||||||
"p99": 6416.4
|
|
||||||
},
|
|
||||||
"longTasks": {
|
|
||||||
"count": 34,
|
|
||||||
"top": [
|
|
||||||
{
|
|
||||||
"startTime": 12843.900000002235,
|
|
||||||
"duration": 5032,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 25604.5,
|
|
||||||
"duration": 3618,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 21451,
|
|
||||||
"duration": 3524,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 34259.10000000149,
|
|
||||||
"duration": 3267,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 30382.300000000745,
|
|
||||||
"duration": 3012,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3396.2000000029802,
|
|
||||||
"duration": 2633,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 6155.400000002235,
|
|
||||||
"duration": 2255,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 19590.10000000149,
|
|
||||||
"duration": 1285,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 17883.60000000149,
|
|
||||||
"duration": 1281,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 8914.300000000745,
|
|
||||||
"duration": 653,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 9985.5,
|
|
||||||
"duration": 651,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 11118.60000000149,
|
|
||||||
"duration": 603,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 8423.300000000745,
|
|
||||||
"duration": 370,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 24981.70000000298,
|
|
||||||
"duration": 323,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 29579.20000000298,
|
|
||||||
"duration": 222,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 30099.400000002235,
|
|
||||||
"duration": 200,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 29914.10000000149,
|
|
||||||
"duration": 179,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 25344,
|
|
||||||
"duration": 177,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 21260.20000000298,
|
|
||||||
"duration": 140,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 29228.300000000745,
|
|
||||||
"duration": 124,
|
|
||||||
"name": "self"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"consoleErrors": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 664 KiB |
|
Before Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 160 KiB |
@@ -1,486 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/",
|
|
||||||
"runDir": "perf/results/2026-06-12T05-30-10-213Z",
|
|
||||||
"elapsedMs": 184508,
|
|
||||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36",
|
|
||||||
"title": "Roomba Rover",
|
|
||||||
"finalUrl": "http://127.0.0.1:5173/",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 8,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"afterLoad": {
|
|
||||||
"Timestamp": 21026.536855,
|
|
||||||
"Documents": 3,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 213,
|
|
||||||
"Nodes": 310,
|
|
||||||
"LayoutCount": 4,
|
|
||||||
"RecalcStyleCount": 6,
|
|
||||||
"LayoutDuration": 0.351349,
|
|
||||||
"RecalcStyleDuration": 0.164,
|
|
||||||
"ScriptDuration": 8.025346,
|
|
||||||
"TaskDuration": 12.3993,
|
|
||||||
"JSHeapUsedSize": 8055732,
|
|
||||||
"JSHeapTotalSize": 9846784
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"Timestamp": 21172.00552,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 776,
|
|
||||||
"Nodes": 2366,
|
|
||||||
"LayoutCount": 28,
|
|
||||||
"RecalcStyleCount": 51,
|
|
||||||
"LayoutDuration": 2.921363,
|
|
||||||
"RecalcStyleDuration": 2.465639,
|
|
||||||
"ScriptDuration": 125.009213,
|
|
||||||
"TaskDuration": 154.711298,
|
|
||||||
"JSHeapUsedSize": 49187212,
|
|
||||||
"JSHeapTotalSize": 81772544
|
|
||||||
},
|
|
||||||
"delta": {
|
|
||||||
"Timestamp": 145.46866500000033,
|
|
||||||
"Documents": -2,
|
|
||||||
"Frames": 0,
|
|
||||||
"JSEventListeners": 563,
|
|
||||||
"Nodes": 2056,
|
|
||||||
"LayoutCount": 24,
|
|
||||||
"RecalcStyleCount": 45,
|
|
||||||
"LayoutDuration": 2.570014,
|
|
||||||
"RecalcStyleDuration": 2.3016389999999998,
|
|
||||||
"ScriptDuration": 116.983867,
|
|
||||||
"TaskDuration": 142.311998,
|
|
||||||
"JSHeapUsedSize": 41131480,
|
|
||||||
"JSHeapTotalSize": 71925760
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cpuSummary": {
|
|
||||||
"totalProfileMs": 109222.492,
|
|
||||||
"sampleCount": 82521,
|
|
||||||
"topSelfTime": [
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=7eee8a21",
|
|
||||||
"functionName": "exports.jsxDEV",
|
|
||||||
"lineNumber": 238,
|
|
||||||
"columnNumber": 31,
|
|
||||||
"selfMs": 50684.10400000017,
|
|
||||||
"samples": 40987
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(program)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 11988.050000000003,
|
|
||||||
"samples": 3951
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/chunk-YX2XZWS3.js?v=7eee8a21",
|
|
||||||
"functionName": "exports.createElement",
|
|
||||||
"lineNumber": 760,
|
|
||||||
"columnNumber": 38,
|
|
||||||
"selfMs": 6174.760000000029,
|
|
||||||
"samples": 5020
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "createTask",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 4341.4680000000035,
|
|
||||||
"samples": 3581
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=7eee8a21",
|
|
||||||
"functionName": "ReactElement",
|
|
||||||
"lineNumber": 126,
|
|
||||||
"columnNumber": 27,
|
|
||||||
"selfMs": 4147.347000000001,
|
|
||||||
"samples": 3387
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "run",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 2896.0120000000024,
|
|
||||||
"samples": 2303
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(idle)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 1528.1939999999993,
|
|
||||||
"samples": 1205
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(garbage collector)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 1454.5849999999996,
|
|
||||||
"samples": 1076
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=7eee8a21",
|
|
||||||
"functionName": "getTaskName",
|
|
||||||
"lineNumber": 80,
|
|
||||||
"columnNumber": 26,
|
|
||||||
"selfMs": 1229.8500000000001,
|
|
||||||
"samples": 1028
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "runWithFiberInDEV",
|
|
||||||
"lineNumber": 992,
|
|
||||||
"columnNumber": 32,
|
|
||||||
"selfMs": 982.0620000000005,
|
|
||||||
"samples": 808
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "now",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 786.4740000000004,
|
|
||||||
"samples": 634
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "validateProperty",
|
|
||||||
"lineNumber": 2344,
|
|
||||||
"columnNumber": 31,
|
|
||||||
"selfMs": 754.6519999999999,
|
|
||||||
"samples": 611
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "updatedAncestorInfoDev",
|
|
||||||
"lineNumber": 1990,
|
|
||||||
"columnNumber": 37,
|
|
||||||
"selfMs": 746.4530000000003,
|
|
||||||
"samples": 607
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=7eee8a21",
|
|
||||||
"functionName": "jsxDEVImpl",
|
|
||||||
"lineNumber": 167,
|
|
||||||
"columnNumber": 25,
|
|
||||||
"selfMs": 731.4129999999996,
|
|
||||||
"samples": 598
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "react_stack_bottom_frame",
|
|
||||||
"lineNumber": 18504,
|
|
||||||
"columnNumber": 42,
|
|
||||||
"selfMs": 652.937,
|
|
||||||
"samples": 533
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/chunk-YX2XZWS3.js?v=7eee8a21",
|
|
||||||
"functionName": "ReactElement",
|
|
||||||
"lineNumber": 165,
|
|
||||||
"columnNumber": 27,
|
|
||||||
"selfMs": 580.7530000000002,
|
|
||||||
"samples": 462
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "updateProperties",
|
|
||||||
"lineNumber": 14785,
|
|
||||||
"columnNumber": 31,
|
|
||||||
"selfMs": 517.7620000000002,
|
|
||||||
"samples": 424
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "createWorkInProgress",
|
|
||||||
"lineNumber": 3630,
|
|
||||||
"columnNumber": 35,
|
|
||||||
"selfMs": 508.02600000000007,
|
|
||||||
"samples": 409
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "ReactStrictModeWarnings.recordLegacyContextWarning",
|
|
||||||
"lineNumber": 18468,
|
|
||||||
"columnNumber": 67,
|
|
||||||
"selfMs": 495.80399999999986,
|
|
||||||
"samples": 409
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "commitUpdate",
|
|
||||||
"lineNumber": 15886,
|
|
||||||
"columnNumber": 27,
|
|
||||||
"selfMs": 477.684,
|
|
||||||
"samples": 370
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "beginWork",
|
|
||||||
"lineNumber": 8415,
|
|
||||||
"columnNumber": 24,
|
|
||||||
"selfMs": 446.36999999999983,
|
|
||||||
"samples": 356
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "reconcileChildrenArray",
|
|
||||||
"lineNumber": 4935,
|
|
||||||
"columnNumber": 39,
|
|
||||||
"selfMs": 436.57999999999987,
|
|
||||||
"samples": 350
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/src/components/ChatMessageRow/index.jsx",
|
|
||||||
"functionName": "ChatMessageRow",
|
|
||||||
"lineNumber": 195,
|
|
||||||
"columnNumber": 38,
|
|
||||||
"selfMs": 413.4399999999997,
|
|
||||||
"samples": 344
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=7eee8a21",
|
|
||||||
"functionName": "getComponentNameFromType",
|
|
||||||
"lineNumber": 12,
|
|
||||||
"columnNumber": 39,
|
|
||||||
"selfMs": 408.42100000000005,
|
|
||||||
"samples": 327
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "addObjectDiffToProperties",
|
|
||||||
"lineNumber": 3080,
|
|
||||||
"columnNumber": 40,
|
|
||||||
"selfMs": 388.74499999999983,
|
|
||||||
"samples": 298
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "setValueForStyles",
|
|
||||||
"lineNumber": 2231,
|
|
||||||
"columnNumber": 32,
|
|
||||||
"selfMs": 388.087,
|
|
||||||
"samples": 311
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "measure",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 382.2370000000001,
|
|
||||||
"samples": 317
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "(anonymous)",
|
|
||||||
"lineNumber": 5231,
|
|
||||||
"columnNumber": 23,
|
|
||||||
"selfMs": 372.06799999999976,
|
|
||||||
"samples": 277
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"functionName": "logComponentRender",
|
|
||||||
"lineNumber": 3166,
|
|
||||||
"columnNumber": 33,
|
|
||||||
"selfMs": 342.3039999999999,
|
|
||||||
"samples": 277
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:5173/src/context/SessionContext.jsx",
|
|
||||||
"functionName": "(anonymous)",
|
|
||||||
"lineNumber": 331,
|
|
||||||
"columnNumber": 20,
|
|
||||||
"selfMs": 337.47,
|
|
||||||
"samples": 275
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"responsiveness": {
|
|
||||||
"longTaskCount": 83,
|
|
||||||
"topLongTasks": [
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 81802.5,
|
|
||||||
"duration": 17746
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 54463,
|
|
||||||
"duration": 6694
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 99677.39999999851,
|
|
||||||
"duration": 6024
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 21612.599999997765,
|
|
||||||
"duration": 5858
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 110758.5,
|
|
||||||
"duration": 5803
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 62108,
|
|
||||||
"duration": 5772
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 76151.89999999851,
|
|
||||||
"duration": 5650
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 125421.19999999925,
|
|
||||||
"duration": 5648
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 145249.3999999985,
|
|
||||||
"duration": 5587
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 132005.09999999776,
|
|
||||||
"duration": 5514
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 68337.5,
|
|
||||||
"duration": 5496
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 118182.59999999776,
|
|
||||||
"duration": 5266
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 40730,
|
|
||||||
"duration": 5224
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 138897,
|
|
||||||
"duration": 5148
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 151428.19999999925,
|
|
||||||
"duration": 5134
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 106092.80000000075,
|
|
||||||
"duration": 4665
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 32223.39999999851,
|
|
||||||
"duration": 4328
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 4601.89999999851,
|
|
||||||
"duration": 3794
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 8585.39999999851,
|
|
||||||
"duration": 3021
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 16030.39999999851,
|
|
||||||
"duration": 2773
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frameCount": 152,
|
|
||||||
"averageFrameMs": 1041.5151315789474,
|
|
||||||
"p95FrameMs": 5816.499999999985,
|
|
||||||
"p99FrameMs": 10849.5,
|
|
||||||
"intervalDriftCount": 37,
|
|
||||||
"averageIntervalDriftMs": 3979.5216216216418,
|
|
||||||
"p95IntervalDriftMs": 13818.400000002235,
|
|
||||||
"p99IntervalDriftMs": 29643.89999999851
|
|
||||||
},
|
|
||||||
"consoleMessages": [
|
|
||||||
{
|
|
||||||
"type": "debug",
|
|
||||||
"text": "[vite] connecting...",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:5173/@vite/client",
|
|
||||||
"line": 732,
|
|
||||||
"column": 8,
|
|
||||||
"lineNumber": 732,
|
|
||||||
"columnNumber": 8
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "debug",
|
|
||||||
"text": "[vite] connected.",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:5173/@vite/client",
|
|
||||||
"line": 826,
|
|
||||||
"column": 11,
|
|
||||||
"lineNumber": 826,
|
|
||||||
"columnNumber": 11
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"text": "%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=7eee8a21",
|
|
||||||
"line": 20102,
|
|
||||||
"column": 53,
|
|
||||||
"lineNumber": 20102,
|
|
||||||
"columnNumber": 53
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"text": "[socket] connecting to https://rover.otter.land",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:5173/src/lib/socket.js",
|
|
||||||
"line": 7,
|
|
||||||
"column": 8,
|
|
||||||
"lineNumber": 7,
|
|
||||||
"columnNumber": 8
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 404 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"failedRequests": [],
|
|
||||||
"errorResponses": [
|
|
||||||
{
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"status": 404
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,532 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/",
|
|
||||||
"runDir": "perf/results/2026-06-12T05-34-37-713Z-root-runtime",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 6,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true,
|
|
||||||
"sampleMs": 30000
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"Timestamp": 21313.290308,
|
|
||||||
"AudioHandlers": 0,
|
|
||||||
"AudioWorkletProcessors": 0,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 1405,
|
|
||||||
"LayoutObjects": 243,
|
|
||||||
"MediaKeySessions": 0,
|
|
||||||
"MediaKeys": 0,
|
|
||||||
"Nodes": 353,
|
|
||||||
"Resources": 0,
|
|
||||||
"ContextLifecycleStateObservers": 7,
|
|
||||||
"V8PerContextDatas": 2,
|
|
||||||
"WorkerGlobalScopes": 0,
|
|
||||||
"UACSSResources": 0,
|
|
||||||
"RTCPeerConnections": 0,
|
|
||||||
"ResourceFetchers": 1,
|
|
||||||
"AdSubframes": 0,
|
|
||||||
"DetachedScriptStates": 2,
|
|
||||||
"ArrayBufferContents": 1,
|
|
||||||
"LayoutCount": 5,
|
|
||||||
"RecalcStyleCount": 6,
|
|
||||||
"LayoutDuration": 0.274398,
|
|
||||||
"RecalcStyleDuration": 0.205702,
|
|
||||||
"DevToolsCommandDuration": 0.000402,
|
|
||||||
"ScriptDuration": 22.219943,
|
|
||||||
"V8CompileDuration": 0,
|
|
||||||
"TaskDuration": 31.430573,
|
|
||||||
"TaskOtherDuration": 8.730128,
|
|
||||||
"ThreadTime": 31.88879,
|
|
||||||
"ProcessTime": 36.72,
|
|
||||||
"JSHeapUsedSize": 5442836,
|
|
||||||
"JSHeapTotalSize": 9699328,
|
|
||||||
"FirstMeaningfulPaint": 0,
|
|
||||||
"DomContentLoaded": 21283.254568,
|
|
||||||
"NavigationStart": 21280.58028
|
|
||||||
},
|
|
||||||
"snapshot": {
|
|
||||||
"elapsedMs": 32642,
|
|
||||||
"listeners": [
|
|
||||||
{
|
|
||||||
"key": "add:input:invalid",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:WebSocket:message",
|
|
||||||
"count": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:auxclick",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:click",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:contextmenu",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:copy",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:cut",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drag",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragend",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragenter",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragexit",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragleave",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragover",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragstart",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drop",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:gotpointercapture",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:input",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keydown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keypress",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keyup",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:lostpointercapture",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mousedown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mousemove",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mouseout",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mouseover",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mouseup",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:paste",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:pointercancel",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:pointerdown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:pointermove",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:pointerout",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:pointerover",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:pointerup",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:reset",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:submit",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:touchcancel",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:touchend",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:touchstart",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:touchmove",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:wheel",
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timers": [
|
|
||||||
{
|
|
||||||
"key": "timeout:15000",
|
|
||||||
"count": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:5000",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:0",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:1000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:2000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:1299",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:1507",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeouts": 17,
|
|
||||||
"intervals": 2,
|
|
||||||
"timeoutFires": 9,
|
|
||||||
"intervalFires": 42,
|
|
||||||
"webSocketMessageCount": 0,
|
|
||||||
"webSocketBytes": 0,
|
|
||||||
"webSocketEvents": [],
|
|
||||||
"mutationsTotal": {
|
|
||||||
"records": 6000,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6000,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
"mutationTargets": [
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:name",
|
|
||||||
"count": 8054
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-emerald-400:attributes:name",
|
|
||||||
"count": 8054
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:type",
|
|
||||||
"count": 4027
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-emerald-400:attributes:type",
|
|
||||||
"count": 4027
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:name",
|
|
||||||
"count": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.flex-1:attributes:name",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.chat-composer-input:attributes:name",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input:attributes:name",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:type",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "Text:characterData:",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input:attributes:type",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:value",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationBursts": [
|
|
||||||
{
|
|
||||||
"at": 26663,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26667,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26670,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26676,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26679,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26682,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26684,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26689,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26696,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26700,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26704,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26711,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26715,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26719,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26726,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26730,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26732,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26737,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26744,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 26748,
|
|
||||||
"records": 6,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 6,
|
|
||||||
"text": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frames": {
|
|
||||||
"count": 1678,
|
|
||||||
"average": 19.307985697258644,
|
|
||||||
"p95": 16.700000000000728,
|
|
||||||
"p99": 16.80000000000291
|
|
||||||
},
|
|
||||||
"longTasks": {
|
|
||||||
"count": 8,
|
|
||||||
"top": [
|
|
||||||
{
|
|
||||||
"startTime": 625.4000000022352,
|
|
||||||
"duration": 2057,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 2705.400000002235,
|
|
||||||
"duration": 1120,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3825.900000002235,
|
|
||||||
"duration": 400,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 4491.699999999255,
|
|
||||||
"duration": 196,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 4237.300000000745,
|
|
||||||
"duration": 158,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 4931.5,
|
|
||||||
"duration": 115,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 4828.699999999255,
|
|
||||||
"duration": 101,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 25127.300000000745,
|
|
||||||
"duration": 67,
|
|
||||||
"name": "self"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"consoleErrors": [
|
|
||||||
"connect_error undefined websocket error undefined",
|
|
||||||
"connect_error undefined websocket error undefined",
|
|
||||||
"connect_error undefined websocket error undefined",
|
|
||||||
"connect_error undefined websocket error undefined",
|
|
||||||
"connect_error undefined websocket error undefined",
|
|
||||||
"connect_error undefined websocket error undefined",
|
|
||||||
"connect_error undefined websocket error undefined"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 336 KiB |
|
Before Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 86 KiB |
@@ -1,652 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/",
|
|
||||||
"runDir": "perf/results/2026-06-12T05-37-11-679Z",
|
|
||||||
"elapsedMs": 48144,
|
|
||||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36",
|
|
||||||
"title": "Roomba Rover",
|
|
||||||
"finalUrl": "http://127.0.0.1:4173/",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 8,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"afterLoad": {
|
|
||||||
"Timestamp": 21442.422344,
|
|
||||||
"Documents": 3,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 333,
|
|
||||||
"Nodes": 364,
|
|
||||||
"LayoutCount": 5,
|
|
||||||
"RecalcStyleCount": 7,
|
|
||||||
"LayoutDuration": 0.567739,
|
|
||||||
"RecalcStyleDuration": 0.239019,
|
|
||||||
"ScriptDuration": 5.57054,
|
|
||||||
"TaskDuration": 7.681989,
|
|
||||||
"JSHeapUsedSize": 3910380,
|
|
||||||
"JSHeapTotalSize": 6815744
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"Timestamp": 21481.571566,
|
|
||||||
"Documents": 3,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 3020,
|
|
||||||
"Nodes": 424,
|
|
||||||
"LayoutCount": 10,
|
|
||||||
"RecalcStyleCount": 20,
|
|
||||||
"LayoutDuration": 0.71593,
|
|
||||||
"RecalcStyleDuration": 0.592888,
|
|
||||||
"ScriptDuration": 25.51692,
|
|
||||||
"TaskDuration": 45.394233,
|
|
||||||
"JSHeapUsedSize": 7257516,
|
|
||||||
"JSHeapTotalSize": 8486912
|
|
||||||
},
|
|
||||||
"delta": {
|
|
||||||
"Timestamp": 39.14922200000001,
|
|
||||||
"Documents": 0,
|
|
||||||
"Frames": 0,
|
|
||||||
"JSEventListeners": 2687,
|
|
||||||
"Nodes": 60,
|
|
||||||
"LayoutCount": 5,
|
|
||||||
"RecalcStyleCount": 13,
|
|
||||||
"LayoutDuration": 0.14819099999999996,
|
|
||||||
"RecalcStyleDuration": 0.353869,
|
|
||||||
"ScriptDuration": 19.946379999999998,
|
|
||||||
"TaskDuration": 37.712244,
|
|
||||||
"JSHeapUsedSize": 3347136,
|
|
||||||
"JSHeapTotalSize": 1671168
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cpuSummary": {
|
|
||||||
"totalProfileMs": 36856.206,
|
|
||||||
"sampleCount": 27449,
|
|
||||||
"topSelfTime": [
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(program)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 16008.739999999993,
|
|
||||||
"samples": 10998
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "postMessage",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 1400.7229999999997,
|
|
||||||
"samples": 1076
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(idle)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 1159.0759999999996,
|
|
||||||
"samples": 921
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "lt",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 7349,
|
|
||||||
"selfMs": 939.4450000000005,
|
|
||||||
"samples": 747
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "ad",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 7898,
|
|
||||||
"selfMs": 678.3770000000004,
|
|
||||||
"samples": 534
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "Nc",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 94591,
|
|
||||||
"selfMs": 648.5520000000006,
|
|
||||||
"samples": 533
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "(anonymous)",
|
|
||||||
"lineNumber": 17,
|
|
||||||
"columnNumber": 58982,
|
|
||||||
"selfMs": 609.3419999999993,
|
|
||||||
"samples": 479
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "requestAnimationFrame",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 569.4970000000001,
|
|
||||||
"samples": 461
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "C9",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 94304,
|
|
||||||
"selfMs": 480.7910000000001,
|
|
||||||
"samples": 384
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "Z4",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 28258,
|
|
||||||
"selfMs": 403.8930000000001,
|
|
||||||
"samples": 305
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "qa",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 23434,
|
|
||||||
"selfMs": 401.37100000000004,
|
|
||||||
"samples": 328
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "queueMicrotask",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 378.9960000000001,
|
|
||||||
"samples": 289
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "B6",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 34145,
|
|
||||||
"selfMs": 375.95699999999994,
|
|
||||||
"samples": 307
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "a2",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 71,
|
|
||||||
"selfMs": 349.62499999999983,
|
|
||||||
"samples": 295
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "c5",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 34654,
|
|
||||||
"selfMs": 345.6060000000002,
|
|
||||||
"samples": 274
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "K4",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 79484,
|
|
||||||
"selfMs": 314.27200000000005,
|
|
||||||
"samples": 240
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "we",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 73874,
|
|
||||||
"selfMs": 306.5540000000002,
|
|
||||||
"samples": 251
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "C1",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 40723,
|
|
||||||
"selfMs": 284.553,
|
|
||||||
"samples": 221
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "Eh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 95724,
|
|
||||||
"selfMs": 280.467,
|
|
||||||
"samples": 229
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "Kl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 117211,
|
|
||||||
"selfMs": 277.51900000000006,
|
|
||||||
"samples": 215
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "U8",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 7096,
|
|
||||||
"selfMs": 276.48700000000014,
|
|
||||||
"samples": 222
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "z2",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 49302,
|
|
||||||
"selfMs": 276.39199999999994,
|
|
||||||
"samples": 206
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "Kt",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 48095,
|
|
||||||
"selfMs": 273.0230000000001,
|
|
||||||
"samples": 220
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(garbage collector)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 268.44300000000004,
|
|
||||||
"samples": 207
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "d1",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 70367,
|
|
||||||
"selfMs": 268.08499999999987,
|
|
||||||
"samples": 219
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "zl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 80827,
|
|
||||||
"selfMs": 263.652,
|
|
||||||
"samples": 209
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "now",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 259.23800000000006,
|
|
||||||
"samples": 210
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "Fl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 101199,
|
|
||||||
"selfMs": 229.16799999999992,
|
|
||||||
"samples": 178
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"functionName": "$",
|
|
||||||
"lineNumber": 1,
|
|
||||||
"columnNumber": 10516,
|
|
||||||
"selfMs": 228.36400000000006,
|
|
||||||
"samples": 178
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "removeAttribute",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 224.73000000000005,
|
|
||||||
"samples": 182
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"responsiveness": {
|
|
||||||
"longTaskCount": 19,
|
|
||||||
"topLongTasks": [
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 577.8000000007451,
|
|
||||||
"duration": 2326
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 2934,
|
|
||||||
"duration": 1390
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 4326.39999999851,
|
|
||||||
"duration": 671
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "unknown",
|
|
||||||
"startTime": 8756.5,
|
|
||||||
"duration": 354
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 5309,
|
|
||||||
"duration": 284
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 5012.89999999851,
|
|
||||||
"duration": 204
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 5968.5,
|
|
||||||
"duration": 199
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 28664.800000000745,
|
|
||||||
"duration": 144
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 5824.800000000745,
|
|
||||||
"duration": 142
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 29231.199999999255,
|
|
||||||
"duration": 118
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "unknown",
|
|
||||||
"startTime": 9170.599999997765,
|
|
||||||
"duration": 107
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 29349.599999997765,
|
|
||||||
"duration": 95
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 28837.89999999851,
|
|
||||||
"duration": 89
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 27044.800000000745,
|
|
||||||
"duration": 80
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 9278.300000000745,
|
|
||||||
"duration": 73
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 30803.39999999851,
|
|
||||||
"duration": 70
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 5632.39999999851,
|
|
||||||
"duration": 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 9110.800000000745,
|
|
||||||
"duration": 59
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 9434.800000000745,
|
|
||||||
"duration": 53
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frameCount": 2103,
|
|
||||||
"averageFrameMs": 22.205515929624347,
|
|
||||||
"p95FrameMs": 16.799999999999272,
|
|
||||||
"p99FrameMs": 100,
|
|
||||||
"intervalDriftCount": 155,
|
|
||||||
"averageIntervalDriftMs": 52.86322580645642,
|
|
||||||
"p95IntervalDriftMs": 113.59999999776483,
|
|
||||||
"p99IntervalDriftMs": 2354.099999997765
|
|
||||||
},
|
|
||||||
"consoleMessages": [
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"text": "[socket] connecting to http://127.0.0.1:4173",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63768,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63768
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "WebSocket connection to 'ws://127.0.0.1:4173/socket.io/?EIO=4&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "connect_error undefined websocket error undefined",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63988,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63988
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "WebSocket connection to 'ws://127.0.0.1:4173/socket.io/?EIO=4&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "connect_error undefined websocket error undefined",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63988,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63988
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "WebSocket connection to 'ws://127.0.0.1:4173/socket.io/?EIO=4&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "connect_error undefined websocket error undefined",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63988,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63988
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "WebSocket connection to 'ws://127.0.0.1:4173/socket.io/?EIO=4&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "connect_error undefined websocket error undefined",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63988,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63988
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "WebSocket connection to 'ws://127.0.0.1:4173/socket.io/?EIO=4&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "connect_error undefined websocket error undefined",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63988,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63988
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "WebSocket connection to 'ws://127.0.0.1:4173/socket.io/?EIO=4&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "connect_error undefined websocket error undefined",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63988,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63988
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "WebSocket connection to 'ws://127.0.0.1:4173/socket.io/?EIO=4&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "connect_error undefined websocket error undefined",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63988,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63988
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "WebSocket connection to 'ws://127.0.0.1:4173/socket.io/?EIO=4&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "connect_error undefined websocket error undefined",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63988,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63988
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "WebSocket connection to 'ws://127.0.0.1:4173/socket.io/?EIO=4&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "connect_error undefined websocket error undefined",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63988,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63988
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "WebSocket connection to 'ws://127.0.0.1:4173/socket.io/?EIO=4&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "connect_error undefined websocket error undefined",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-DhJId5qi.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63988,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63988
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"failedRequests": [],
|
|
||||||
"errorResponses": []
|
|
||||||
}
|
|
||||||
@@ -1,849 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/",
|
|
||||||
"runDir": "perf/results/2026-06-12T06-07-57-124Z-root-runtime",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 6,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true,
|
|
||||||
"sampleMs": 10000
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"Timestamp": 23292.158609,
|
|
||||||
"AudioHandlers": 0,
|
|
||||||
"AudioWorkletProcessors": 0,
|
|
||||||
"Documents": 3,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 398,
|
|
||||||
"LayoutObjects": 1945,
|
|
||||||
"MediaKeySessions": 0,
|
|
||||||
"MediaKeys": 0,
|
|
||||||
"Nodes": 2266,
|
|
||||||
"Resources": 18,
|
|
||||||
"ContextLifecycleStateObservers": 13,
|
|
||||||
"V8PerContextDatas": 4,
|
|
||||||
"WorkerGlobalScopes": 0,
|
|
||||||
"UACSSResources": 0,
|
|
||||||
"RTCPeerConnections": 0,
|
|
||||||
"ResourceFetchers": 3,
|
|
||||||
"AdSubframes": 0,
|
|
||||||
"DetachedScriptStates": 2,
|
|
||||||
"ArrayBufferContents": 3,
|
|
||||||
"LayoutCount": 12,
|
|
||||||
"RecalcStyleCount": 30,
|
|
||||||
"LayoutDuration": 0.928481,
|
|
||||||
"RecalcStyleDuration": 0.991796,
|
|
||||||
"DevToolsCommandDuration": 0.000323,
|
|
||||||
"ScriptDuration": 6.326745,
|
|
||||||
"V8CompileDuration": 0,
|
|
||||||
"TaskDuration": 11.079249,
|
|
||||||
"TaskOtherDuration": 2.831904,
|
|
||||||
"ThreadTime": 11.911973,
|
|
||||||
"ProcessTime": 14.42,
|
|
||||||
"JSHeapUsedSize": 8961292,
|
|
||||||
"JSHeapTotalSize": 12058624,
|
|
||||||
"FirstMeaningfulPaint": 0,
|
|
||||||
"DomContentLoaded": 23282.130574,
|
|
||||||
"NavigationStart": 23279.964199
|
|
||||||
},
|
|
||||||
"snapshot": {
|
|
||||||
"elapsedMs": 20101.5,
|
|
||||||
"listeners": [
|
|
||||||
{
|
|
||||||
"key": "add:input:invalid",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:online",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:document:visibilitychange",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:online",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:document:visibilitychange",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:canplay",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:ended",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:error",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:pause",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:play",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:stalled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:waiting",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:auxclick",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:click",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:contextmenu",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:copy",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:cut",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drag",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragend",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragenter",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragexit",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragleave",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragover",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragstart",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drop",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:gotpointercapture",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:input",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keydown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keypress",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keyup",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:lostpointercapture",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mousedown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mousemove",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mouseout",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mouseover",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mouseup",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:paste",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:pointercancel",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:pointerdown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:pointermove",
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timers": [
|
|
||||||
{
|
|
||||||
"key": "timeout:0",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:250",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:60000",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:12000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:200",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:15000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:1000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:2000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:4000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:10000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:3000",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeouts": 10,
|
|
||||||
"intervals": 12,
|
|
||||||
"timeoutFires": 4,
|
|
||||||
"intervalFires": 45,
|
|
||||||
"webSocketMessageCount": 285,
|
|
||||||
"webSocketBytes": 5610397,
|
|
||||||
"webSocketEvents": [
|
|
||||||
{
|
|
||||||
"event": "log:entry",
|
|
||||||
"count": 150,
|
|
||||||
"bytes": 35714
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "sensorFrame",
|
|
||||||
"count": 76,
|
|
||||||
"bytes": 142196
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "session:sync",
|
|
||||||
"count": 18,
|
|
||||||
"bytes": 306294
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "commandAck",
|
|
||||||
"count": 12,
|
|
||||||
"bytes": 1128
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "",
|
|
||||||
"count": 4,
|
|
||||||
"bytes": 4992972
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverSnapshot:frame",
|
|
||||||
"count": 3,
|
|
||||||
"bytes": 291
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "0{\"sid\":\"CQG",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "40{\"sid\":\"-s",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "mode",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 27
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "controlGranted",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "alert",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 128
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "alert:new",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 165
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "auth:role",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 31
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "rovers",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 5453
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "chat:init",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 77613
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "overseer:memory",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 520
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "log:init",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 44230
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "barcode:state",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 1627
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "kinect:pointCloudFrame",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 262
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "kinect:colorFrame",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 169
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "430[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "431[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 194
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "432[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 133
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverHostStats",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 766
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "2",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "433[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 145
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverSnapshot:status",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 79
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "WALL-E",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 40
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationsTotal": {
|
|
||||||
"records": 4729,
|
|
||||||
"added": 175,
|
|
||||||
"removed": 11,
|
|
||||||
"attrs": 4524,
|
|
||||||
"text": 19
|
|
||||||
},
|
|
||||||
"mutationTargets": [
|
|
||||||
{
|
|
||||||
"key": "input.accent-emerald-400:attributes:name",
|
|
||||||
"count": 1866
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-emerald-400:attributes:type",
|
|
||||||
"count": 933
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.h-full:attributes:style",
|
|
||||||
"count": 580
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:d",
|
|
||||||
"count": 292
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:fill",
|
|
||||||
"count": 290
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:name",
|
|
||||||
"count": 242
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:type",
|
|
||||||
"count": 121
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.flex-1:attributes:name",
|
|
||||||
"count": 28
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:name",
|
|
||||||
"count": 28
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "Text:characterData:",
|
|
||||||
"count": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.chat-composer-input:attributes:name",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-cyan-500:attributes:name",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input:attributes:name",
|
|
||||||
"count": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:type",
|
|
||||||
"count": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "section.panel-section.border:attributes:style",
|
|
||||||
"count": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.min-w-0.flex-1:attributes:name",
|
|
||||||
"count": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-cyan-500:attributes:type",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "header.flex.items-center:attributes:style",
|
|
||||||
"count": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input:attributes:type",
|
|
||||||
"count": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:stroke",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.flex:attributes:disabled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.min-h-9:attributes:disabled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:value",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "span.inline-flex.items-center:attributes:class",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.flex-1:attributes:value",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:playsinline",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:autoplay",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:disableremoteplayback",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.rounded-sm.px-2:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.flex.flex-col:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:placeholder",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.button-dark.min-w-0:attributes:disabled",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.fixed.bottom-2:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.flex:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.mobile-touch-control.flex:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:value",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.button-dark.text-sm:attributes:disabled",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "audio:attributes:autoplay",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "audio:attributes:disableremoteplayback",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationBursts": [
|
|
||||||
{
|
|
||||||
"at": 7974,
|
|
||||||
"records": 241,
|
|
||||||
"added": 123,
|
|
||||||
"removed": 6,
|
|
||||||
"attrs": 110,
|
|
||||||
"text": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 11575,
|
|
||||||
"records": 147,
|
|
||||||
"added": 11,
|
|
||||||
"removed": 2,
|
|
||||||
"attrs": 132,
|
|
||||||
"text": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 12385,
|
|
||||||
"records": 131,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 127,
|
|
||||||
"text": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 12981,
|
|
||||||
"records": 119,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 119,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 18332,
|
|
||||||
"records": 117,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 116,
|
|
||||||
"text": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 17140,
|
|
||||||
"records": 111,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 111,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 13371,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 13636,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 13804,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 14271,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15643,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15909,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 18503,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15238,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15283,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15415,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15495,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 15947,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 16169,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 16317,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frames": {
|
|
||||||
"count": 86,
|
|
||||||
"average": 206.1918604651163,
|
|
||||||
"p95": 566.5999999999995,
|
|
||||||
"p99": 3399.8999999999996
|
|
||||||
},
|
|
||||||
"longTasks": {
|
|
||||||
"count": 61,
|
|
||||||
"top": [
|
|
||||||
{
|
|
||||||
"startTime": 5290.5,
|
|
||||||
"duration": 2689,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 365.4000000022352,
|
|
||||||
"duration": 1807,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 2182.900000002235,
|
|
||||||
"duration": 1038,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 10371.10000000149,
|
|
||||||
"duration": 709,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 9852,
|
|
||||||
"duration": 519,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 8202.60000000149,
|
|
||||||
"duration": 394,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3221.699999999255,
|
|
||||||
"duration": 385,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3882.5,
|
|
||||||
"duration": 356,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 11221.5,
|
|
||||||
"duration": 353,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 18042.900000002235,
|
|
||||||
"duration": 292,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 12110.900000002235,
|
|
||||||
"duration": 276,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 12738.800000000745,
|
|
||||||
"duration": 243,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 16946.5,
|
|
||||||
"duration": 195,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 11575.5,
|
|
||||||
"duration": 186,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3615.60000000149,
|
|
||||||
"duration": 185,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 14671.699999999255,
|
|
||||||
"duration": 160,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 4239.199999999255,
|
|
||||||
"duration": 130,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 14837.5,
|
|
||||||
"duration": 110,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 15717.60000000149,
|
|
||||||
"duration": 108,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 17352.400000002235,
|
|
||||||
"duration": 107,
|
|
||||||
"name": "self"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"consoleErrors": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 680 KiB |
@@ -1,900 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/",
|
|
||||||
"runDir": "perf/results/2026-06-12T06-09-03-973Z-root-runtime",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 8,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true,
|
|
||||||
"sampleMs": 30000
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"Timestamp": 23379.874681,
|
|
||||||
"AudioHandlers": 0,
|
|
||||||
"AudioWorkletProcessors": 0,
|
|
||||||
"Documents": 3,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 683,
|
|
||||||
"LayoutObjects": 1975,
|
|
||||||
"MediaKeySessions": 0,
|
|
||||||
"MediaKeys": 0,
|
|
||||||
"Nodes": 2710,
|
|
||||||
"Resources": 20,
|
|
||||||
"ContextLifecycleStateObservers": 20,
|
|
||||||
"V8PerContextDatas": 4,
|
|
||||||
"WorkerGlobalScopes": 0,
|
|
||||||
"UACSSResources": 0,
|
|
||||||
"RTCPeerConnections": 4,
|
|
||||||
"ResourceFetchers": 3,
|
|
||||||
"AdSubframes": 0,
|
|
||||||
"DetachedScriptStates": 2,
|
|
||||||
"ArrayBufferContents": 12,
|
|
||||||
"LayoutCount": 49,
|
|
||||||
"RecalcStyleCount": 77,
|
|
||||||
"LayoutDuration": 1.49198,
|
|
||||||
"RecalcStyleDuration": 1.886372,
|
|
||||||
"DevToolsCommandDuration": 0.000349,
|
|
||||||
"ScriptDuration": 14.419283,
|
|
||||||
"V8CompileDuration": 0,
|
|
||||||
"TaskDuration": 29.584723,
|
|
||||||
"TaskOtherDuration": 11.786739,
|
|
||||||
"ThreadTime": 32.430498,
|
|
||||||
"ProcessTime": 38.63,
|
|
||||||
"JSHeapUsedSize": 7496328,
|
|
||||||
"JSHeapTotalSize": 13107200,
|
|
||||||
"FirstMeaningfulPaint": 0,
|
|
||||||
"DomContentLoaded": 23349.78059,
|
|
||||||
"NavigationStart": 23346.788167
|
|
||||||
},
|
|
||||||
"snapshot": {
|
|
||||||
"elapsedMs": 38566.699999999255,
|
|
||||||
"listeners": [
|
|
||||||
{
|
|
||||||
"key": "add:input:invalid",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:window:online",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:document:visibilitychange",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:canplay",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:ended",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:error",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:pause",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:play",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:stalled",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:audio:waiting",
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:window:online",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:document:visibilitychange",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:audio:play",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:audio:pause",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:audio:ended",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:audio:error",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:audio:waiting",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:audio:canplay",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove:audio:stalled",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:WebSocket:message",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:auxclick",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:click",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:contextmenu",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:copy",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:cut",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drag",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragend",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragenter",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragexit",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragleave",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragover",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:dragstart",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:drop",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:gotpointercapture",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:input",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keydown",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keypress",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:keyup",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:lostpointercapture",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "add:div#root:mousedown",
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timers": [
|
|
||||||
{
|
|
||||||
"key": "timeout:0",
|
|
||||||
"count": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:250",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:200",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:2000",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:12000",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:60000",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:15000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:4000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:3000",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:1000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "interval:2000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:10000",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:1282",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timeout:1257",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeouts": 19,
|
|
||||||
"intervals": 15,
|
|
||||||
"timeoutFires": 10,
|
|
||||||
"intervalFires": 121,
|
|
||||||
"webSocketMessageCount": 533,
|
|
||||||
"webSocketBytes": 11199188,
|
|
||||||
"webSocketEvents": [
|
|
||||||
{
|
|
||||||
"event": "log:entry",
|
|
||||||
"count": 308,
|
|
||||||
"bytes": 71889
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "sensorFrame",
|
|
||||||
"count": 90,
|
|
||||||
"bytes": 168271
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "commandAck",
|
|
||||||
"count": 36,
|
|
||||||
"bytes": 3396
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "session:sync",
|
|
||||||
"count": 33,
|
|
||||||
"bytes": 560916
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "",
|
|
||||||
"count": 14,
|
|
||||||
"bytes": 10130394
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverSnapshot:frame",
|
|
||||||
"count": 10,
|
|
||||||
"bytes": 967
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "mode",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 54
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "controlGranted",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 82
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "alert",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 256
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "alert:new",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 330
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "auth:role",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 62
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "rovers",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 10906
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "chat:init",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 155226
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "overseer:memory",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 1040
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "log:init",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 89130
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "barcode:state",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 3254
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "kinect:pointCloudFrame",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 524
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "kinect:colorFrame",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 338
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverSnapshot:status",
|
|
||||||
"count": 2,
|
|
||||||
"bytes": 156
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "0{\"sid\":\"a3i",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "40{\"sid\":\"Bp",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "430[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "431[{\"succes",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 194
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "2",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "432[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 133
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "433[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 145
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "WALL-E",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "0{\"sid\":\"3kq",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "40{\"sid\":\"BX",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "436[{\"error\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "437[{\"error\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "438[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 133
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "439[{\"url\":\"",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 145
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "Freaky",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"event": "roverHostStats",
|
|
||||||
"count": 1,
|
|
||||||
"bytes": 767
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationsTotal": {
|
|
||||||
"records": 6037,
|
|
||||||
"added": 199,
|
|
||||||
"removed": 35,
|
|
||||||
"attrs": 5767,
|
|
||||||
"text": 36
|
|
||||||
},
|
|
||||||
"mutationTargets": [
|
|
||||||
{
|
|
||||||
"key": "input.accent-emerald-400:attributes:name",
|
|
||||||
"count": 2648
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-emerald-400:attributes:type",
|
|
||||||
"count": 1324
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.h-full:attributes:style",
|
|
||||||
"count": 512
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:name",
|
|
||||||
"count": 338
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:d",
|
|
||||||
"count": 262
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:fill",
|
|
||||||
"count": 256
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:type",
|
|
||||||
"count": 169
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "Text:characterData:",
|
|
||||||
"count": 36
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.flex-1:attributes:name",
|
|
||||||
"count": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:name",
|
|
||||||
"count": 28
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-cyan-500:attributes:name",
|
|
||||||
"count": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "section.panel-section.border:attributes:style",
|
|
||||||
"count": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.chat-composer-input:attributes:name",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "header.flex.items-center:attributes:style",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "path.[object.SVGAnimatedString]:attributes:stroke",
|
|
||||||
"count": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input:attributes:name",
|
|
||||||
"count": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:type",
|
|
||||||
"count": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.accent-cyan-500:attributes:type",
|
|
||||||
"count": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.min-w-0.flex-1:attributes:name",
|
|
||||||
"count": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input:attributes:type",
|
|
||||||
"count": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.rounded-sm.px-2:attributes:class",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.fixed.bottom-2:attributes:class",
|
|
||||||
"count": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:value",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "span.inline-flex.items-center:attributes:class",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.flex:attributes:disabled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.min-h-9:attributes:disabled",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.mobile-touch-control.flex:attributes:class",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.mobile-touch-control.flex:attributes:class",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input#replay-sources-mobile-portrait-title.field-input.min-w-0:attributes:placeholder",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:playsinline",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:autoplay",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "video.h-full.w-full:attributes:disableremoteplayback",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "audio:attributes:autoplay",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "audio:attributes:disableremoteplayback",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.flex-1:attributes:value",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "span.inline-block.rounded:attributes:style",
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "div.flex.flex-col:attributes:class",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.button-dark.min-w-0:attributes:disabled",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "input.field-input.w-full:attributes:value",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "button.button-dark.text-sm:attributes:disabled",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mutationBursts": [
|
|
||||||
{
|
|
||||||
"at": 10936,
|
|
||||||
"records": 241,
|
|
||||||
"added": 123,
|
|
||||||
"removed": 6,
|
|
||||||
"attrs": 110,
|
|
||||||
"text": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 27789,
|
|
||||||
"records": 177,
|
|
||||||
"added": 9,
|
|
||||||
"removed": 17,
|
|
||||||
"attrs": 145,
|
|
||||||
"text": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 17412,
|
|
||||||
"records": 133,
|
|
||||||
"added": 2,
|
|
||||||
"removed": 2,
|
|
||||||
"attrs": 118,
|
|
||||||
"text": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 31614,
|
|
||||||
"records": 111,
|
|
||||||
"added": 2,
|
|
||||||
"removed": 1,
|
|
||||||
"attrs": 104,
|
|
||||||
"text": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 37740,
|
|
||||||
"records": 100,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 1,
|
|
||||||
"attrs": 99,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 38360,
|
|
||||||
"records": 99,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 99,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 17915,
|
|
||||||
"records": 59,
|
|
||||||
"added": 11,
|
|
||||||
"removed": 2,
|
|
||||||
"attrs": 45,
|
|
||||||
"text": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 19317,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 20163,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 20363,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 23753,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 24543,
|
|
||||||
"records": 51,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 51,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 31831,
|
|
||||||
"records": 51,
|
|
||||||
"added": 11,
|
|
||||||
"removed": 2,
|
|
||||||
"attrs": 37,
|
|
||||||
"text": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 18579,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 18763,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 19129,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 19374,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 21234,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 22011,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"at": 22767,
|
|
||||||
"records": 47,
|
|
||||||
"added": 0,
|
|
||||||
"removed": 0,
|
|
||||||
"attrs": 47,
|
|
||||||
"text": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frames": {
|
|
||||||
"count": 181,
|
|
||||||
"average": 185.53591160220995,
|
|
||||||
"p95": 349.90000000000146,
|
|
||||||
"p99": 1849.9000000000015
|
|
||||||
},
|
|
||||||
"longTasks": {
|
|
||||||
"count": 165,
|
|
||||||
"top": [
|
|
||||||
{
|
|
||||||
"startTime": 7151.60000000149,
|
|
||||||
"duration": 3795,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 459.6000000014901,
|
|
||||||
"duration": 2544,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 3019.199999999255,
|
|
||||||
"duration": 1426,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 29673.900000002235,
|
|
||||||
"duration": 1066,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 14211,
|
|
||||||
"duration": 972,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 28901.800000000745,
|
|
||||||
"duration": 772,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 13490.699999999255,
|
|
||||||
"duration": 720,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 16782.400000002235,
|
|
||||||
"duration": 639,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 11240.199999999255,
|
|
||||||
"duration": 552,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 31092.5,
|
|
||||||
"duration": 524,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 4445.60000000149,
|
|
||||||
"duration": 495,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 5394.800000000745,
|
|
||||||
"duration": 459,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 27348.199999999255,
|
|
||||||
"duration": 445,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 24835.10000000149,
|
|
||||||
"duration": 363,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 4963.800000000745,
|
|
||||||
"duration": 272,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 37513.60000000149,
|
|
||||||
"duration": 228,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 17919.5,
|
|
||||||
"duration": 215,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 27801.60000000149,
|
|
||||||
"duration": 193,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 17425.10000000149,
|
|
||||||
"duration": 179,
|
|
||||||
"name": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startTime": 31885.900000002235,
|
|
||||||
"duration": 176,
|
|
||||||
"name": "self"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"consoleErrors": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 703 KiB |
|
Before Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 169 KiB |
@@ -1,529 +0,0 @@
|
|||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/",
|
|
||||||
"runDir": "perf/results/2026-06-12T06-11-56-994Z",
|
|
||||||
"elapsedMs": 83940,
|
|
||||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36",
|
|
||||||
"title": "Roomba Rover",
|
|
||||||
"finalUrl": "http://127.0.0.1:4173/",
|
|
||||||
"environment": {
|
|
||||||
"cpuThrottle": 8,
|
|
||||||
"viewport": {
|
|
||||||
"width": 390,
|
|
||||||
"height": 844
|
|
||||||
},
|
|
||||||
"isMobile": true
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"afterLoad": {
|
|
||||||
"Timestamp": 23527.948042,
|
|
||||||
"Documents": 3,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 267,
|
|
||||||
"Nodes": 651,
|
|
||||||
"LayoutCount": 5,
|
|
||||||
"RecalcStyleCount": 7,
|
|
||||||
"LayoutDuration": 0.500736,
|
|
||||||
"RecalcStyleDuration": 0.283234,
|
|
||||||
"ScriptDuration": 5.693685,
|
|
||||||
"TaskDuration": 7.662781,
|
|
||||||
"JSHeapUsedSize": 4764568,
|
|
||||||
"JSHeapTotalSize": 7077888
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"Timestamp": 23593.417486,
|
|
||||||
"Documents": 1,
|
|
||||||
"Frames": 1,
|
|
||||||
"JSEventListeners": 1032,
|
|
||||||
"Nodes": 2696,
|
|
||||||
"LayoutCount": 137,
|
|
||||||
"RecalcStyleCount": 280,
|
|
||||||
"LayoutDuration": 3.566888,
|
|
||||||
"RecalcStyleDuration": 3.703827,
|
|
||||||
"ScriptDuration": 27.965461,
|
|
||||||
"TaskDuration": 67.992237,
|
|
||||||
"JSHeapUsedSize": 12832824,
|
|
||||||
"JSHeapTotalSize": 19759104
|
|
||||||
},
|
|
||||||
"delta": {
|
|
||||||
"Timestamp": 65.46944399999848,
|
|
||||||
"Documents": -2,
|
|
||||||
"Frames": 0,
|
|
||||||
"JSEventListeners": 765,
|
|
||||||
"Nodes": 2045,
|
|
||||||
"LayoutCount": 132,
|
|
||||||
"RecalcStyleCount": 273,
|
|
||||||
"LayoutDuration": 3.066152,
|
|
||||||
"RecalcStyleDuration": 3.420593,
|
|
||||||
"ScriptDuration": 22.271776000000003,
|
|
||||||
"TaskDuration": 60.329456,
|
|
||||||
"JSHeapUsedSize": 8068256,
|
|
||||||
"JSHeapTotalSize": 12681216
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cpuSummary": {
|
|
||||||
"totalProfileMs": 55106.451,
|
|
||||||
"sampleCount": 40511,
|
|
||||||
"topSelfTime": [
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(program)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 26825.782999999923,
|
|
||||||
"samples": 18512
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(idle)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 4815.981000000005,
|
|
||||||
"samples": 3701
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "(anonymous)",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 40267,
|
|
||||||
"selfMs": 2656.4819999999972,
|
|
||||||
"samples": 2026
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "ad",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 7898,
|
|
||||||
"selfMs": 1032.9479999999999,
|
|
||||||
"samples": 818
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "Kt",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 48104,
|
|
||||||
"selfMs": 951.7199999999993,
|
|
||||||
"samples": 747
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "Ce",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 94317,
|
|
||||||
"selfMs": 905.2510000000005,
|
|
||||||
"samples": 727
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "tryParse",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 49539,
|
|
||||||
"selfMs": 592.3200000000003,
|
|
||||||
"samples": 480
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(garbage collector)",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 578.5280000000001,
|
|
||||||
"samples": 430
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "start",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 1485400,
|
|
||||||
"selfMs": 445.5410000000001,
|
|
||||||
"samples": 322
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "G4",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 28256,
|
|
||||||
"selfMs": 432.7939999999998,
|
|
||||||
"samples": 325
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "lt",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 7348,
|
|
||||||
"selfMs": 360.4799999999999,
|
|
||||||
"samples": 279
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "ga",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 10651,
|
|
||||||
"selfMs": 330.2019999999997,
|
|
||||||
"samples": 260
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "r",
|
|
||||||
"lineNumber": 1,
|
|
||||||
"columnNumber": 967,
|
|
||||||
"selfMs": 323.32300000000004,
|
|
||||||
"samples": 239
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "Rl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 101212,
|
|
||||||
"selfMs": 308.80500000000006,
|
|
||||||
"samples": 225
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "S1",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 40721,
|
|
||||||
"selfMs": 305.08099999999996,
|
|
||||||
"samples": 243
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "C2",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 49311,
|
|
||||||
"selfMs": 304.76900000000006,
|
|
||||||
"samples": 250
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "ws.onmessage",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 33453,
|
|
||||||
"selfMs": 284.5850000000001,
|
|
||||||
"samples": 204
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "N2.emit",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 25371,
|
|
||||||
"selfMs": 275.96699999999987,
|
|
||||||
"samples": 219
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "Xl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 117224,
|
|
||||||
"selfMs": 260.1920000000001,
|
|
||||||
"samples": 208
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "pa",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 10399,
|
|
||||||
"selfMs": 249.32600000000016,
|
|
||||||
"samples": 180
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "S4",
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 91365,
|
|
||||||
"selfMs": 237.0250000000001,
|
|
||||||
"samples": 179
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "N6",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 34144,
|
|
||||||
"selfMs": 234.33099999999988,
|
|
||||||
"samples": 188
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "bl",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 80838,
|
|
||||||
"selfMs": 228.66600000000017,
|
|
||||||
"samples": 171
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "o2",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 42232,
|
|
||||||
"selfMs": 219.2690000000001,
|
|
||||||
"samples": 182
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "setAttribute",
|
|
||||||
"lineNumber": -1,
|
|
||||||
"columnNumber": -1,
|
|
||||||
"selfMs": 213.44599999999997,
|
|
||||||
"samples": 178
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"functionName": "(anonymous)",
|
|
||||||
"lineNumber": 29,
|
|
||||||
"columnNumber": 16,
|
|
||||||
"selfMs": 194.01300000000003,
|
|
||||||
"samples": 148
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "s2",
|
|
||||||
"lineNumber": 9,
|
|
||||||
"columnNumber": 71,
|
|
||||||
"selfMs": 189.78200000000004,
|
|
||||||
"samples": 160
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "na",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 3730,
|
|
||||||
"selfMs": 184.6409999999999,
|
|
||||||
"samples": 146
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "Eh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 95737,
|
|
||||||
"selfMs": 179.85799999999998,
|
|
||||||
"samples": 138
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"functionName": "Oh",
|
|
||||||
"lineNumber": 8,
|
|
||||||
"columnNumber": 116259,
|
|
||||||
"selfMs": 179.09700000000007,
|
|
||||||
"samples": 134
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"responsiveness": {
|
|
||||||
"longTaskCount": 333,
|
|
||||||
"topLongTasks": [
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 7254.699999999255,
|
|
||||||
"duration": 3731
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 409.19999999925494,
|
|
||||||
"duration": 2518
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "unknown",
|
|
||||||
"startTime": 13437.400000002235,
|
|
||||||
"duration": 1801
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 2960.199999999255,
|
|
||||||
"duration": 1382
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 24715.300000000745,
|
|
||||||
"duration": 1038
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 54470.699999999255,
|
|
||||||
"duration": 1002
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 53685.199999999255,
|
|
||||||
"duration": 785
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 23959.699999999255,
|
|
||||||
"duration": 755
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 50997,
|
|
||||||
"duration": 747
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 4343.10000000149,
|
|
||||||
"duration": 621
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 27087.5,
|
|
||||||
"duration": 597
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 22357.300000000745,
|
|
||||||
"duration": 554
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "unknown",
|
|
||||||
"startTime": 15496,
|
|
||||||
"duration": 478
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 5324.5,
|
|
||||||
"duration": 474
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 11302.300000000745,
|
|
||||||
"duration": 448
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 40393.300000000745,
|
|
||||||
"duration": 435
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 55742.5,
|
|
||||||
"duration": 405
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 25906.300000000745,
|
|
||||||
"duration": 323
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 38613.5,
|
|
||||||
"duration": 303
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "self",
|
|
||||||
"startTime": 46738.5,
|
|
||||||
"duration": 286
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frameCount": 358,
|
|
||||||
"averageFrameMs": 207.5804469273743,
|
|
||||||
"p95FrameMs": 483.3000000000029,
|
|
||||||
"p99FrameMs": 2016.6,
|
|
||||||
"intervalDriftCount": 43,
|
|
||||||
"averageIntervalDriftMs": 1537.434883720913,
|
|
||||||
"p95IntervalDriftMs": 7938.5,
|
|
||||||
"p99IntervalDriftMs": 8055.300000000745
|
|
||||||
},
|
|
||||||
"consoleMessages": [
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"text": "[socket] connecting to https://rover.otter.land",
|
|
||||||
"location": {
|
|
||||||
"url": "http://127.0.0.1:4173/assets/index-BcKZ4JVW.js",
|
|
||||||
"line": 11,
|
|
||||||
"column": 63824,
|
|
||||||
"lineNumber": 11,
|
|
||||||
"columnNumber": 63824
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 404 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 400 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble/whep",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 400 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble-audio/whep",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 400 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble/whep",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "error",
|
|
||||||
"text": "Failed to load resource: the server responded with a status of 400 ()",
|
|
||||||
"location": {
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble/whep",
|
|
||||||
"line": 0,
|
|
||||||
"column": 0,
|
|
||||||
"lineNumber": 0,
|
|
||||||
"columnNumber": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"failedRequests": [
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble/whep",
|
|
||||||
"method": "POST",
|
|
||||||
"failure": "net::ERR_ABORTED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble-audio/whep",
|
|
||||||
"method": "POST",
|
|
||||||
"failure": "net::ERR_ABORTED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble/whep",
|
|
||||||
"method": "POST",
|
|
||||||
"failure": "net::ERR_ABORTED"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"errorResponses": [
|
|
||||||
{
|
|
||||||
"url": "https://media.discordapp.net/attachments/1424887548622405672/1512626709692682340/cat2.PNG?ex=6a24c6e6&is=6a237566&hm=baeec4c8e666888d89711065b1dbcb391c3415f9c28d1917bdd286776b60c867&=&format=webp&quality=lossless",
|
|
||||||
"status": 404
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble/whep",
|
|
||||||
"status": 400
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble-audio/whep",
|
|
||||||
"status": 400
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble/whep",
|
|
||||||
"status": 400
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://rover.otter.land/video/Bweeble/whep",
|
|
||||||
"status": 400
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 461 KiB |