-
-
Notifications
You must be signed in to change notification settings - Fork 505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Large Memory in the browser #1089
Comments
Hi @Kamil-Och! Can you provide some more details or a code example reproducing the problem? |
Okay here is sample code based on my use case: codefrom nicegui import ui
class Robot:
def __init__(self) -> None:
self.current_robot_state = 1
self.goal_robot_state = 1
self.frames_sent = 0
self.loop_counter_overtime_error = 0
self.gripper_type = 0
self.payload = 0.0
self.test_var = 0
class Joint:
def __init__(self,
goal_torque: float = 0.0,
goal_fsm: int = 0,
goal_position: float = 0.0,
goal_id_torque: float = 0.0,
goal_rl_torque: float = 0.0,
goal_rl_pid: float = 0.0,
goal_rl_friction: float = 0.0,
current_position: float = 0.0,
current_velocity: float = 0.0,
goal_velocity: float = 0.0,
current_torque: float = 0.0,
current_fsm: int = 0,
current_bearing_temperature: int = 0,
current_motor_temperature: int = 0,
current_warnings: int = 0,
current_errors: int = 0,
joint_registers: list [int] = [0]*256,
joint_rcv_counter: int = 0,
joint_rcv_counter_error: int = 0):
self.goal_torque = goal_torque
self.goal_fsm = goal_fsm
self.goal_position = goal_position
self.goal_id_torque = goal_id_torque
self.goal_rl_torque = goal_rl_torque
self.goal_rl_pid = goal_rl_pid
self.goal_rl_friction = goal_rl_friction
self.current_position = current_position
self.current_velocity = current_velocity
self.goal_velocity = goal_velocity
self.current_torque = current_torque
self.current_fsm = current_fsm
self.current_bearing_temperature = current_bearing_temperature
self.current_motor_temperature = current_motor_temperature
self.current_warnings = current_warnings
self.current_errors = current_errors
self.joint_registers = joint_registers
self.joint_rcv_counter = joint_rcv_counter
self.joint_rcv_counter_error = joint_rcv_counter_error
def updateData():
robot.current_robot_state += 1
robot.frames_sent += 1
robot.goal_robot_state += 1
robot.gripper_type += 1
robot.loop_counter_overtime_error += 1
robot.test_var += 1
robot.payload += 1
for joint in joints:
joint.goal_torque += 1
joint.goal_fsm += 1
joint.goal_position += 1
joint.goal_id_torque += 1
joint.goal_rl_torque += 1
joint.goal_rl_pid += 1
joint.goal_rl_friction += 1
joint.current_position += 1
joint.current_velocity += 1
joint.goal_velocity += 1
joint.current_torque += 1
joint.current_fsm += 1
joint.current_bearing_temperature += 1
joint.current_motor_temperature += 1
joint.current_warnings += 1
joint.current_errors += 1
joint.joint_rcv_counter += 1
joint.joint_rcv_counter_error += 1
def reload_data_and_refresh():
updateData()
refreshUI()
def refreshUI():
UI.refresh()
@ui.refreshable
def UI():
ui.label("Robot")
with ui.row():
ui.label("robot.current_robot_state")
ui.label(robot.current_robot_state)
ui.label("robot.frames_sent")
ui.label(robot.frames_sent)
ui.label("robot.goal_robot_state")
ui.label(robot.goal_robot_state)
ui.label("robot.gripper_type")
ui.label(robot.gripper_type)
ui.label("robot.loop_counter_overtime_error")
ui.label(robot.loop_counter_overtime_error)
ui.label("robot.test_var")
ui.label(robot.test_var)
ui.label("robot.payload")
ui.label(robot.payload)
ui.label("Joints")
with ui.row():
for joint in joints:
with ui.grid(columns=2):
ui.label("joint.goal_torque")
ui.label(joint.goal_torque)
ui.label("joint.goal_fsm")
ui.label(joint.goal_fsm)
ui.label("joint.goal_position")
ui.label(joint.goal_position)
ui.label("joint.goal_id_torque")
ui.label(joint.goal_id_torque)
ui.label("joint.goal_rl_torque")
ui.label(joint.goal_rl_torque)
ui.label("joint.goal_rl_pid")
ui.label(joint.goal_rl_pid)
ui.label("joint.goal_rl_friction")
ui.label(joint.goal_rl_friction)
ui.label("joint.current_position")
ui.label(joint.current_position)
ui.label("joint.current_velocity")
ui.label(joint.current_velocity)
ui.label("joint.goal_velocity")
ui.label(joint.goal_velocity)
ui.label("joint.current_torque")
ui.label(joint.current_torque)
ui.label("joint.current_fsm")
ui.label(joint.current_fsm)
ui.label("joint.current_bearing_temperature")
ui.label(joint.current_bearing_temperature)
ui.label("joint.current_motor_temperature")
ui.label(joint.current_motor_temperature)
ui.label("joint.current_warnings")
ui.label(joint.current_warnings)
ui.label("joint.current_errors")
ui.label(joint.current_errors)
ui.label("joint.joint_rcv_counter")
ui.label(joint.joint_rcv_counter)
ui.label("joint.joint_rcv_counter_error")
ui.label(joint.joint_rcv_counter_error)
if __name__ in {"__main__", "__mp_main__"}:
if __name__ == "__mp_main__":
robot = Robot()
joints = [Joint() for _ in range(6)]
ui.run(title='HMI', show=False)
try:
ui_timer = ui.timer(0.1, lambda: reload_data_and_refresh())
UI()
except KeyboardInterrupt:
print('KeyboardInterrupt')
exit(0) |
I can't reproduce the huge memory consumption you're describing. But apart from that you main block looks strange. Creating the UI should happen before calling if __name__ in {"__main__", "__mp_main__"}:
if __name__ == "__mp_main__":
robot = Robot()
joints = [Joint() for _ in range(6)]
UI()
ui_timer = ui.timer(0.1, lambda: reload_data_and_refresh())
ui.run(title='HMI', show=False) Does the problem still occur? |
yea it doesn't seams to change anything. When I check with firefox process Manager in the span of little over 2 minutes memory for this code goes from 200 mb to 4 gb and then ether crash tab or freeze browser. Considering that you can't reproduce the error could it be the problem with my setup or browser? |
Oh! Now I looked at the right place: The memory usage of "Google Chrome Helper (Renderer)" grows indeed about 1 GB per minute. Here is a more compact reproduction: import time
from nicegui import ui
@ui.refreshable
def labels():
for _ in range(1000):
ui.label(time.time())
labels()
ui_timer = ui.timer(0.1, labels.refresh)
ui.run() Looks like |
But without import time
from nicegui import ui
def render():
container.clear()
with container:
for _ in range(1000):
ui.label(time.time())
container = ui.column()
ui_timer = ui.timer(0.1, render)
ui.run() |
i just tried without the ui.refreshable and it works fine to be honest it shows me around 200 mb of memory codefrom nicegui import ui
class Robot:
def __init__(self) -> None:
self.current_robot_state = 1
self.goal_robot_state = 1
self.frames_sent = 0
self.loop_counter_overtime_error = 0
self.gripper_type = 0
self.payload = 0.0
self.test_var = 0
class Joint:
def __init__(self,
goal_torque: float = 0.0,
goal_fsm: int = 0,
goal_position: float = 0.0,
goal_id_torque: float = 0.0,
goal_rl_torque: float = 0.0,
goal_rl_pid: float = 0.0,
goal_rl_friction: float = 0.0,
current_position: float = 0.0,
current_velocity: float = 0.0,
goal_velocity: float = 0.0,
current_torque: float = 0.0,
current_fsm: int = 0,
current_bearing_temperature: int = 0,
current_motor_temperature: int = 0,
current_warnings: int = 0,
current_errors: int = 0,
joint_registers: list [int] = [0]*256,
joint_rcv_counter: int = 0,
joint_rcv_counter_error: int = 0):
self.goal_torque = goal_torque
self.goal_fsm = goal_fsm
self.goal_position = goal_position
self.goal_id_torque = goal_id_torque
self.goal_rl_torque = goal_rl_torque
self.goal_rl_pid = goal_rl_pid
self.goal_rl_friction = goal_rl_friction
self.current_position = current_position
self.current_velocity = current_velocity
self.goal_velocity = goal_velocity
self.current_torque = current_torque
self.current_fsm = current_fsm
self.current_bearing_temperature = current_bearing_temperature
self.current_motor_temperature = current_motor_temperature
self.current_warnings = current_warnings
self.current_errors = current_errors
self.joint_registers = joint_registers
self.joint_rcv_counter = joint_rcv_counter
self.joint_rcv_counter_error = joint_rcv_counter_error
def updateData():
robot.current_robot_state += 1
robot.frames_sent += 1
robot.goal_robot_state += 1
robot.gripper_type += 1
robot.loop_counter_overtime_error += 1
robot.test_var += 1
robot.payload += 1
for joint in joints:
joint.goal_torque += 1
joint.goal_fsm += 1
joint.goal_position += 1
joint.goal_id_torque += 1
joint.goal_rl_torque += 1
joint.goal_rl_pid += 1
joint.goal_rl_friction += 1
joint.current_position += 1
joint.current_velocity += 1
joint.goal_velocity += 1
joint.current_torque += 1
joint.current_fsm += 1
joint.current_bearing_temperature += 1
joint.current_motor_temperature += 1
joint.current_warnings += 1
joint.current_errors += 1
joint.joint_rcv_counter += 1
joint.joint_rcv_counter_error += 1
def reload_data_and_refresh():
updateData()
refreshUI()
def refreshUI():
current_robot_state.set_text(current_robot_state)
frame_sent.set_text(robot.frames_sent)
goal_robot_state.set_text(robot.goal_robot_state)
gripper_type.set_text(robot.gripper_type)
loop_counter_overtime_error.set_text(robot.loop_counter_overtime_error)
test_var.set_text(robot.test_var)
robot_payload.set_text(robot.payload)
i = 0
for joint in joints:
goal_torque[i].set_text(joint.goal_torque)
goal_fsm[i].set_text(joint.goal_fsm)
goal_position[i].set_text(joint.goal_position)
goal_id_torque[i].set_text(joint.goal_id_torque)
goal_rl_torque[i].set_text(joint.goal_rl_torque)
goal_rl_pid[i].set_text(joint.goal_rl_pid)
goal_rl_friction[i].set_text(joint.goal_rl_friction)
current_position[i].set_text(joint.current_position)
current_velocity[i].set_text(joint.current_velocity)
goal_velocity[i].set_text(joint.goal_velocity)
current_torque[i].set_text(joint.current_torque)
current_fsm[i].set_text(joint.current_fsm)
current_bearing_temperature[i].set_text(joint.current_bearing_temperature)
current_motor_temperature[i].set_text(joint.current_motor_temperature)
current_warning[i].set_text(joint.current_warnings)
current_errors[i].set_text(joint.current_errors)
joint_rcv_counter[i].set_text(joint.joint_rcv_counter)
joint_rcv_counter_error[i].set_text(joint_rcv_counter_error)
i += 1
if __name__ in {"__main__", "__mp_main__"}:
robot = Robot()
joints = [Joint() for _ in range(6)]
i = 0
goal_torque = []
goal_fsm = []
goal_position = []
goal_id_torque = []
goal_rl_torque = []
goal_rl_pid = []
goal_rl_friction = []
current_position = []
current_velocity = []
goal_velocity = []
current_torque = []
current_fsm = []
current_bearing_temperature = []
current_motor_temperature = []
current_warning = []
current_errors = []
joint_rcv_counter =[]
joint_rcv_counter_error = []
ui.label("Robot")
with ui.row():
ui.label("robot.current_robot_state")
current_robot_state = ui.label(robot.current_robot_state)
ui.label("robot.frames_sent")
frame_sent = ui.label(robot.frames_sent)
ui.label("robot.goal_robot_state")
goal_robot_state = ui.label(robot.goal_robot_state)
ui.label("robot.gripper_type")
gripper_type = ui.label(robot.gripper_type)
ui.label("robot.loop_counter_overtime_error")
loop_counter_overtime_error = ui.label(robot.loop_counter_overtime_error)
ui.label("robot.test_var")
test_var = ui.label(robot.test_var)
ui.label("robot.payload")
robot_payload = ui.label(robot.payload)
ui.label("Joints")
with ui.row():
for joint in joints:
print(i)
with ui.grid(columns=2):
ui.label("joint.goal_torque")
goal_torque.append(ui.label(joint.goal_torque))
ui.label("joint.goal_fsm")
goal_fsm.append(ui.label(joint.goal_fsm))
ui.label("joint.goal_position")
goal_position.append(ui.label(joint.goal_position))
ui.label("joint.goal_id_torque")
goal_id_torque.append(ui.label(joint.goal_id_torque))
ui.label("joint.goal_rl_torque")
goal_rl_torque.append(ui.label(joint.goal_rl_torque))
ui.label("joint.goal_rl_pid")
goal_rl_pid.append(ui.label(joint.goal_rl_pid))
ui.label("joint.goal_rl_friction")
goal_rl_friction.append(ui.label(joint.goal_rl_friction))
ui.label("joint.current_position")
current_position.append(ui.label(joint.current_position))
ui.label("joint.current_velocity")
current_velocity.append(ui.label(joint.current_velocity))
ui.label("joint.goal_velocity")
goal_velocity.append(ui.label(joint.goal_velocity))
ui.label("joint.current_torque")
current_torque.append(ui.label(joint.current_torque))
ui.label("joint.current_fsm")
current_fsm.append(ui.label(joint.current_fsm))
ui.label("joint.current_bearing_temperature")
current_bearing_temperature.append(ui.label(joint.current_bearing_temperature))
ui.label("joint.current_motor_temperature")
current_motor_temperature.append(ui.label(joint.current_motor_temperature))
ui.label("joint.current_warnings")
current_warning.append(ui.label(joint.current_warnings))
ui.label("joint.current_errors")
current_errors.append(ui.label(joint.current_errors))
ui.label("joint.joint_rcv_counter")
joint_rcv_counter.append(ui.label(joint.joint_rcv_counter))
ui.label("joint.joint_rcv_counter_error")
joint_rcv_counter_error.append(ui.label(joint.joint_rcv_counter_error))
i += 1
ui_timer = ui.timer(0.1, lambda: reload_data_and_refresh())
ui.run(title='HMI', show=False) |
Yes, updating the existing elements is certainly the more efficient way to update the UI. Binding could also help to simplify the code. But nonetheless, removing and re-creating elements should not leak any memory. |
I found at least one memory leak: window.socket.on("update", (msg) => Object.entries(msg).forEach(([id, el]) => this.elements[el.id] = el)); We only add elements to |
I pushed a fix for the leak from my previous comment #1089 (comment) on the "memory" branch: For 1000 labels 10 times per second, the memory consumption still grows quite at bit. I assume something like the garbage collector having trouble to deal with so many objects. But with reduced frequency to once per second the problem seems to be (almost?) gone. Even after many minutes the consumption oscillates around 500..600MB. I am, however, not 100% sure that there isn't any growth at all. Do we want to merge and close the issue for now, or should we keep investigating? By the way: So far I didn't find a way to directly get number of Vue components or the memory profile of the Vue app. This would be a more definitive indicator of a memory leak. |
No, we should not merge. Tests are red because moving elements from one container to another does not work anymore. |
@falkoschindler like this? class Element:
def __del__(self):
print("should be release")
# Notify the frontend that this component should be released.
# self.push_del_message() |
@CrystalWindSnake Yes, something like this might work. But I'm not sure if every element should notify the client about its removal independently, or if we better collect such element IDs as part of the update message. |
Collecting and scheduling seems to be a better approach. it will also be easy to use different schedulers and make different removal dependency strategies. |
New code for experimenting with the client-side element count: def add():
with card:
ui.label('Some text')
async def count():
ui.notify(await ui.run_javascript('return Object.keys(window.app.elements).length'))
card = ui.card()
ui.button('Add element', on_click=add)
ui.button('Remove element', on_click=card.clear)
ui.button('Count', on_click=count) |
I think I fixed this issue in be30664:
This works well with the example from #1089 (comment) and passes all pytests. While experimenting with this stress test I noticed a huge performance bottleneck caused by the dependency loading function. It seems like calling the async function for every updated element (even without custom component or libraries) is much more expensive than checking for a component or library outside: With this change I can update 1000 elements almost 10 times per second on my machine. Of course, real-life scenarios should be much more conservative regarding performance and bandwidth. @rodja Sorry for pushing to the main branch right before a big release. But I think we should include these changes. |
Looks good. |
Description
Hi,
I have an problem that when i inspect the browser the memory for the niceGui website is periodically rising from like 0.5 GB to 5 GB and its causing the website to crash. I don't really know what the problem is but I'm using ui.timer to get the data for the gui on 0.1 seconds timer.
The text was updated successfully, but these errors were encountered: