Memory Leak Debugging Assistance Needed

Does anyone have any experience in finding memory leaks in javascript/web applications. I'm trying to nail down what could potentially be a memory leak in the TabOrder plugin and have no idea where to even start. Additional information of the issue can be found in the plugin's issue tracker here.

Would greatly appreciate any assistance with this, as what might be causing this could also effect several other of my plugins and I want to make sure I'm not introducing poor performance into the platform.

Doing some quick profiling over 400 seconds, this is with all plugins disabled.

And this is with the Tab Order plugin installed.

1 Like

Looking at the ticket it seems you're not sure when the issue started but at least are able to reproduce it. Have you tried the first release?

If it doesn't occur then I'd do a git bisect to help narrow down the change that introduced it.

1 Like

git bisect definitely is your friend here in that scenario, at least if it was a specific commit that introduced the issue. Which would also be a result that helps you narrow this down further.

It's not a memory leak, as the GC is able to release the memory used once it kicks in.
You'll notice that the drops are all around same value:

  1. overall mem usage after GC is same
  2. memory used by created object is recovered

I would describe it more like app steadily creating objects and releasing them.

Looking at the second picture it does seem to grow a bit.
Some kind of timed refresh / reorder or something?

Can you take object dump just after GC?
compare it over-time and that would give an indication to type of objects growing

ex: profile view ->

Thanks @kantlivelong and @foosel, I know the two major changes that could have introduced the issue but it's still uncertain that there is an issue based on the latest comment in the issue tracker. I saw the issue locally once but haven't been able to reproduce the large memory growth that's described of going into the GB usage range since that one time, so not sure if there is a specific trigger that causes it or if it's related specifically to this plugin or not. That's why I kept scratching my head.

Thanks @seb for that article link. I'll look into doing the two snapshots and comparing to see what is being added. I was looking into this last night, and the largest items seemed to be internal OctoPrint stuff and nothing specific to my plugin, but it was hard to verify that because so many things get embedded under knockout, which I assume is related to observables.

The event seems to be popping off every 100 milliseconds. I'm not sure if this is each page or each tab.

When I see code like this I sometimes wonder if there doesn't need to be some sort of "return true", "return false" at the end of some event handler.

Yeah, that's the crazy part. OctoPrint core includes that tabdrop library and has for a long time, which made me think it was related to my "patched" version. However, just profiling OctoPrint without any 3rd party plugins enabled it seems there might be a leak if I'm interpreting this object dump correctly.

I'm about to compare it with profiles with my plugin enabled.

Dunno, but this seems to be running ten times per second. Not sure if this is only during move events within a tab or all the time.

		var notify = function() {
			for(var i=0, cnt=registered.length; i<cnt; i++) {

It might be worth temporarily adding a console log inside there to determine just how often this thing runs and whether it's only during move events or otherwise.

If I'm reading the code correctly it's binding that function to the window.resize event here in the default implementation. My version works the same, but having both of them may be causing a conflict. I'm still not convinced this is a Tab Order plugin issue at this point, because I still have only been able to reproduce once.

I guess what I'm saying is that hooked events in JavaScript are supposed to be told, basically, if I'm authoritative and will handle this myself or if I'm going to add some value and then let the default handler to its thing, too. I don't honestly know what the default state is for this.

It's possible that either or both aren't making this distinction and there's conflict as a result.