Crow & druid: high CPU load due to Python?

I just ran a crow script for about 2 hours, it contained the init function with
input[1].mode( 'stream' )
and one LFO action for one output. druid was active & connected all the time and showing the voltage readout for input 1. The activity meter on macOS 10.14.6 showed about 86% CPU load for Python, and my MacBook’s fan ran quite loudly. Quitting druid returned everything to normal.

Is this expected behavior?

I then restarted druid, and while it said , druid wrote on its own
^^st
What does this mean?

After restart (still running the same minimal script, and druid printing the voltage readout),
it took Python about 2 min to reach 8% CPU load,
after 15 min it is now at 13%,
after 35 min it is at 21%,
after 45 min it is at 25%.

Replacing
input[1].mode( 'stream' )
with
input[1].mode( 'change' )
makes Python’s CPU load drop to 2% almost instantly.

EDITS: added CPU measurements after druid restart.

1 Like

Wow that won’t do, thanks for the report. Probably something on the druid side, something like continually rewriting a slowly expanding memory region maybe. I can look into it.

This is just some unflushed bit of data crow sent to druid. The way the streaming voltage display works is crow sends some text like ^^stream(1, 1.23) from crow’s default input[n].stream handler and druid parses this message to display the voltage(s).

1 Like

@wolfgangschaltung can you share your complete script? So far I’m just seeing a pretty steady ~4-5% CPU and ~25-30MB RAM on Windows 10.

Thanks for looking into this!

This CPU increase only seems to happen if either druid prints the streamed input value “on its own” (i.e. without a print command), which creates just a single line that constantly changes its value, is printed in gray at the top of the druid window, looks like this and…
image
…happens with just this script:

function init()
	input[1].mode('stream')
end

Or it happens when calling a “regular” print function on each stream interval (I hope I described this correctly) like in this script – in this case there is no single gray line at the top of the druid window, but a regular vertical stream of printed values:

function init()
	input[1].mode( 'stream' )
end

function stream_input_1_to_output_1()
 	print(input[1].volts)
 	output[1].volts = input[1].volts
end

input[1].stream = stream_input_1_to_output_1

Hope this helps.

@csboling: This (useful – at least for me :slight_smile: ) utility script bumps druid’s CPU load to >90% in about one minute. After like 30 seconds it starts printing corrupted ^^stream messages all over the place (although no print command exists – please see pic below).

I am on
macOS 10.14.6 with
druid 1.0.0 and
crow 2.0.0

--- CV mixer
-- input 1: patch CV1 into this
-- input 2: patch CV2 into this
-- output 1: gives CV1+CV2

function init()
	input[1].mode( 'stream', 0.001 )
	input[2].mode( 'stream', 0.001 )
end

function mix_inputs_1_and_2_to_output_1()
 	output[1].volts = input[1].volts+input[2].volts
end

input[1].stream = mix_inputs_1_and_2_to_output_1

The channel is getting overwhelmed. Crow I think can handle this stream frequency but I think this is too fast to send data this way (1khz * 20 characters in ^^stream(2,1.000000) * 8 bits = 160,000 bits per second, which exceeds the 115,200 bits per second used by the crow serial port). So what happens is some transmitted data gets corrupted or lost, resulting in druid not capturing these event messages properly, and it winds up putting them in the main output window along with all other “normal” output from crow.

If you’re not trying to monitor input[2] in druid, you could replace the default stream handler (input[2].stream = function() end), or not set input 2 to stream since this isn’t needed to read the current value of input[2].volts – that just checks the input value on demand.

When druid is trying to print data this fast I’m not too surprised to see CPU and RAM usage climb because druid is appending to a larger and larger scrollback buffer. Probably we should limit the scrollback to some maximum size.

1 Like

Thanks for the explanation! I removed the superfluous
input[2].mode( 'stream', 0.001 )
and gone are the gray voltage monitoring readout,
the corrupted stream messages,
and Python is now no longer eating up CPU and RAM. :smiley:

But there is one thing I don’t understand. What defines whether or not this gray voltage monitoring readout is being shown or not? My script still has
input[1].mode( 'stream', 0.001 )
in it, but there is no voltage stream readout in druid.

The core Lua library on crow configures default handlers for each type of input event. The default behavior for the stream event is to send the text ^^stream(...) to the host – tell is a routine that formats and sends these messages out the serial port. The idea is that messages starting with ^^ are special messages to / from crow that host applications can handle differently from how they handle “normal” interaction with the Lua environment. For instance, messages like ^^version or ^^First` that are sent to crow are handled by the crow firmware directly and don’t rely on the Lua environment.

In the case of druid, ^^stream and ^^change messages are handled by updating the content of the top bar of druid, and not echoing this text to the druid output pane, so you can monitor the input values when the default handlers are configured on crow. If you still wanted to use this functionality when you have overridden the default input handlers in your script, you could copy the _c.tell(...) code from the default handlers into your handler functions.

When crow is trying to send text too fast, the ^^stream messages are getting corrupted, so druid is not able to intercept them, and passes them along to the output buffer. This results in filling your druid scrollback with so much text that druid uses a bunch of system resources trying to keep up.

1 Like

Thanks much for taking the time to explain this – I think now I got the difference between the default event handler and a custom event handler.

Just as there are default values that come into effect if I don’t specify them (like time in ASL action lfo(), there are default functionalities that come into effect if I don’t specify custom functionality.

Writing the ^^stream and ^^change messages into druid’s top bar is the default functionality that comes when defining input[x].mode( 'stream‘ ) and input[x].mode( 'change‘ ), but the moment I specify input[x].stream = my_custom_functionality or input[x].change = my_custom_functionality, that default functionality is replaced with my custom functionality.

1 Like