Hi team,
I am running some very simple jruby automation scripts within openhab 4.1.3 on RPi 4 and am experiencing what appears to be some memory leaks within jruby as time goes on.
Linux hapi 5.10.103-v7l+ #1529 SMP Tue Mar 8 12:24:00 GMT 2022 armv7l GNU/Linux
jruby version is 9.4.9.0
I am not using any gems other than openhab-jrubyscripting to interface with openhab
If left unchecked, the JVM will run out of memory within a week in my RPi setup. Inspecting heap dumps, several possible leaks are identified which appear to be within jruby. Details below;
Problem Suspect 1
29,102 instances of org.jruby.RubyClass, loaded by org.openhab.automation.jrubyscripting occupy 195,511,120 (19.70%) bytes.
org.jruby.RubyClass
org.openhab.automation.jrubyscripting
Problem Suspect 2
71,150 instances of org.jruby.ir.IRMethod, loaded by org.openhab.automation.jrubyscripting occupy 148,220,520 (14.93%) bytes.
org.jruby.ir.IRMethod
org.openhab.automation.jrubyscripting
Problem Suspect 3
33 instances of org.jruby.Ruby, loaded by org.openhab.automation.jrubyscripting occupy 118,031,520 (11.89%) bytes.
org.jruby.Ruby
org.openhab.automation.jrubyscripting
I am happy to provide heap dumps privately for any jruby devs that would like to analyse.
Here is the script which I'm using:
# automation of HVAC based on astro, temp & solar forecasts
#
ITEM_DEFAULT_VALUES = {
Hvac_Solar_Power_Threshold_Dusk => 2000 | 'W',
Hvac_Solar_Power_Threshold_Dawn => 3000 | 'W',
Hvac_Solar_Override_Trigger_Cool => 9 | '°C',
Hvac_Solar_Override_Trigger_Heat => 34 | '°C',
Hvac_Solar_Override_Normal_Cool => 13 | '°C',
Hvac_Solar_Override_Normal_Heat => 28 | '°C'
}
DAWN_AVG_MINS = 10
DUSK_AVG_MINS = 10
shared_cache.compute_if_absent(:hvacstate) { OFF }
shared_cache.compute_if_absent(:hvacsolaroverride) { OFF }
def hvac_log(msg)
logger.info "HVAC: UN(#{Airtouch_Unit_Fujitsu_Power.state}) AS(#{shared_cache[:hvacstate]}): #{msg}"
Airtouch_Unit_Fujitsu_Automation_Message.update(msg)
end
def deploy_scenes(power_scene, setpoint_scene)
hvac_log sprintf("deploy_scenes(#{power_scene}, #{setpoint_scene})")
active_scenes = [power_scene, setpoint_scene]
gAirtouchZones.members.each do |zone_group|
zone_group.tags.sort.each do |tag|
if tag =~ /Fujitsu_Scene_(\S+?)\[(\S+)\]\[(\S+)\]$/
scene, tag, state = $1, $2, $3
if active_scenes.include? scene
zone_group.members.flatten.sort_by(&:name).tagged(tag).each do |item|
hvac_log sprintf("[Scene_Item_State] %-40s | %-10s | %-10s --> %s", item.name, scene, tag, state)
item.ensure << state
end
end
end
end
end
end
def determine_automation_scenes(mode)
sunrise_time = Sunrise_Time.state
sunset_time = Sunset_Time.state
tnow = Time.now
# Determine POWER scene
case tnow.wday
when 5 # fri
power_scene = "AllOn"
when 6, 0 # sat, sun
power_scene = "Weekend"
else
power_scene = "Workday"
end
power_scene = 'Night' if Time.now < (sunrise_time - '1 hours') || Time.now > (sunset_time + '5 hours')
# Determine SETPOINT scene
case mode
when 'HEAT'
setpoint_scene = 'Winter'
when 'COOL'
setpoint_scene = 'Summer'
end
#case Season_Name.state
#when 'WINTER','AUTUMN'
# setpoint_scene = 'Winter'
#when 'SPRING','SUMMER'
# setpoint_scene = 'Summer'
#end
[power_scene, setpoint_scene]
end
rule "hvac_startup" do
on_start at_level: 80
run do
for item, value in ITEM_DEFAULT_VALUES
item.state = value if item.state.nil?
end
end
end
rule "hvac_scene_changed" do
changed Airtouch_Unit_Fujitsu_Scene_Power
changed Airtouch_Unit_Fujitsu_Scene_Setpoint
run do
deploy_scenes(Airtouch_Unit_Fujitsu_Scene_Power.state, Airtouch_Unit_Fujitsu_Scene_Setpoint.state)
end
end
rule "hvac_event_off" do
changed Airtouch_Unit_Fujitsu_Power, to: OFF
run do
shared_cache[:hvacstate] = OFF
shared_cache[:hvacsolaroverride] = OFF
logger.info "HVAC: was turned OFF"
end
end
# Force the HVAC off at sunrise, assuming it was manually turned on beforehand.
rule "hvac_sunrise_force_off" do
channel "astro:sun:home:rise#event", attach: "sunrise"
run do
hvac_log "Turning unit off due to SUNRISE event"
Airtouch_Unit_Fujitsu_Power.ensure.off
end
end
#rule "hvac_morning_force_off" do
# every :day, at: '07:00'
# run do
# hvac_log "Turning unit off due to reached 7am"
# Airtouch_Unit_Fujitsu_Power.ensure.off
# end
#end
rule "hvac_automation" do
every 1.minutes
run do |event|
solar_remain = ForecastSolar_9Hens_Remaining.state
inlet_temp = Airtouch_Unit_Fujitsu_Inlet_Temp.state
max_temp_today = BOM_Day1_MaximumTemperature.state
cur_temp = BOM_Day1_AirTemperature.state
apparent_temp = BOM_Day1_ApparentTemperature.state
# Use forecasts and inlet temp to determine if we should HEAT or COOL
if max_temp_today <= 20 || apparent_temp <= 7 || (inlet_temp <= 12 | '°C')
mode = 'HEAT'
elsif max_temp_today >= 27 || apparent_temp >= 28 || (inlet_temp >= 28 | '°C')
mode = 'COOL'
else
mode = nil
end
# Overrides for normal solar automation. Use inlet temp as gauge of general comfort level
if (inlet_temp <= Hvac_Solar_Override_Trigger_Cool.state | '°C') || (inlet_temp >= Hvac_Solar_Override_Trigger_Heat.state | '°C')
shared_cache[:hvacsolaroverride] = ON
end
if Airtouch_Unit_Fujitsu_Automation_Override.state == ON
hvac_log "Manual override active"
else
if Airtouch_Unit_Fujitsu_Power.state == ON
if mode.nil?
hvac_log "Environmentals outside of automation bounds"
if shared_cache[:hvacstate] == ON
hvac_log "Temperatures (cur:#{cur_temp} inlet:#{inlet_temp} max:#{max_temp_today} app:#{apparent_temp}) outside of automation bounds"
hvac_log "turning unit OFF due to automation state being ON"
Airtouch_Unit_Fujitsu_Power.ensure.off
end
else
hvac_log "Sunset = #{Sunset_Time.state}, Season = #{Season_Name.state}, Mode = #{mode}, SolarRemain = #{solar_remain}"
if shared_cache[:hvacstate] == ON
if shared_cache[:hvacsolaroverride] == ON
if ((mode == 'COOL' && inlet_temp <= Hvac_Solar_Override_Normal_Heat.state | '°C') ||
(mode == 'HEAT' && inlet_temp >= Hvac_Solar_Override_Normal_Cool.state | '°C'))
hvac_log "turn unit off NOW: SOLAR OVERRIDE IS ACTIVE AND INLET TEMP #{inlet_temp} IS NOW WITHIN COMFORTABLE RANGE"
Airtouch_Unit_Fujitsu_Power.ensure.off
end
else
sunset_avg_solar_power = Solar_Total_Gen_Power.average_since(DUSK_AVG_MINS.minutes.ago)
if (solar_remain && (solar_remain < 3 | 'kWh')) || ((sunset_avg_solar_power < Hvac_Solar_Power_Threshold_Dusk.state | 'W') || (Time.now > Sunset_Time.state))
hvac_log "Solar energy (#{sunset_avg_solar_power} / 10min) too low OR we're past sunset time: TURN HVAC UNIT OFF NOW"
Airtouch_Unit_Fujitsu_Power.ensure.off
end
end
end
end
else
if mode.nil?
hvac_log "Temperatures (cur:#{cur_temp} inlet:#{inlet_temp} max:#{max_temp_today} app:#{apparent_temp}) outside of automation bounds"
elsif shared_cache[:hvacsolaroverride] == ON || Time.now > Sunrise_Time.state
sunrise_avg_solar_power = Solar_Total_Gen_Power.average_since(DAWN_AVG_MINS.minutes.ago)
hvac_log "Sunrise = #{Sunrise_Time.state}, Season = #{Season_Name.state}, Mode = #{mode}, SolarGen(30mAVG) = #{sunrise_avg_solar_power}, SolarRemain = #{solar_remain}, Temp = cur:#{cur_temp} inlet:#{inlet_temp} max:#{max_temp_today} app:#{apparent_temp}"
if shared_cache[:hvacsolaroverride] == ON || (sunrise_avg_solar_power > Hvac_Solar_Power_Threshold_Dawn.state | 'W') || ((sunrise_avg_solar_power > Hvac_Solar_Power_Threshold_Dawn.state | 'W') && solar_remain && (solar_remain > 20 | 'kWh'))
if shared_cache[:hvacsolaroverride] == ON
hvac_log "turn unit on NOW: SOLAR OVERRIDE IS ACTIVE BECAUSE INLET TEMP #{inlet_temp} IS OUT OF COMFORTABLE RANGE !!!"
else
hvac_log "turn unit on NOW: Average solar generation / solar forecast / temp forecast exceeded thresholds"
end
power_scene, setpoint_scene = determine_automation_scenes(mode)
Airtouch_Unit_Fujitsu_Scene_Power.update(power_scene)
Airtouch_Unit_Fujitsu_Scene_Setpoint.update(setpoint_scene)
Airtouch_Unit_Fujitsu_Mode.ensure.command(mode)
Airtouch_Unit_Fujitsu_Power.ensure.on
hvac_log "Starting timer to set automation state"
sleep 10
shared_cache[:hvacstate] = ON
hvac_log "Mode is #{Airtouch_Unit_Fujitsu_Mode.state}, :hvacstate is #{shared_cache[:hvacstate]}, hvacsolaroverride is #{shared_cache[:hvacsolaroverride]}"
end
end
end
end
end
end
Hi team,
I am running some very simple jruby automation scripts within openhab 4.1.3 on RPi 4 and am experiencing what appears to be some memory leaks within jruby as time goes on.
Linux hapi 5.10.103-v7l+ #1529 SMP Tue Mar 8 12:24:00 GMT 2022 armv7l GNU/Linux
jruby version is 9.4.9.0
I am not using any gems other than openhab-jrubyscripting to interface with openhab
If left unchecked, the JVM will run out of memory within a week in my RPi setup. Inspecting heap dumps, several possible leaks are identified which appear to be within jruby. Details below;
Problem Suspect 1
29,102 instances of org.jruby.RubyClass, loaded by org.openhab.automation.jrubyscripting occupy 195,511,120 (19.70%) bytes.
org.jruby.RubyClass
org.openhab.automation.jrubyscripting
Problem Suspect 2
71,150 instances of org.jruby.ir.IRMethod, loaded by org.openhab.automation.jrubyscripting occupy 148,220,520 (14.93%) bytes.
org.jruby.ir.IRMethod
org.openhab.automation.jrubyscripting
Problem Suspect 3
33 instances of org.jruby.Ruby, loaded by org.openhab.automation.jrubyscripting occupy 118,031,520 (11.89%) bytes.
org.jruby.Ruby
org.openhab.automation.jrubyscripting
I am happy to provide heap dumps privately for any jruby devs that would like to analyse.
Here is the script which I'm using:
# automation of HVAC based on astro, temp & solar forecasts # ITEM_DEFAULT_VALUES = { Hvac_Solar_Power_Threshold_Dusk => 2000 | 'W', Hvac_Solar_Power_Threshold_Dawn => 3000 | 'W', Hvac_Solar_Override_Trigger_Cool => 9 | '°C', Hvac_Solar_Override_Trigger_Heat => 34 | '°C', Hvac_Solar_Override_Normal_Cool => 13 | '°C', Hvac_Solar_Override_Normal_Heat => 28 | '°C' } DAWN_AVG_MINS = 10 DUSK_AVG_MINS = 10 shared_cache.compute_if_absent(:hvacstate) { OFF } shared_cache.compute_if_absent(:hvacsolaroverride) { OFF } def hvac_log(msg) logger.info "HVAC: UN(#{Airtouch_Unit_Fujitsu_Power.state}) AS(#{shared_cache[:hvacstate]}): #{msg}" Airtouch_Unit_Fujitsu_Automation_Message.update(msg) end def deploy_scenes(power_scene, setpoint_scene) hvac_log sprintf("deploy_scenes(#{power_scene}, #{setpoint_scene})") active_scenes = [power_scene, setpoint_scene] gAirtouchZones.members.each do |zone_group| zone_group.tags.sort.each do |tag| if tag =~ /Fujitsu_Scene_(\S+?)\[(\S+)\]\[(\S+)\]$/ scene, tag, state = $1, $2, $3 if active_scenes.include? scene zone_group.members.flatten.sort_by(&:name).tagged(tag).each do |item| hvac_log sprintf("[Scene_Item_State] %-40s | %-10s | %-10s --> %s", item.name, scene, tag, state) item.ensure << state end end end end end end def determine_automation_scenes(mode) sunrise_time = Sunrise_Time.state sunset_time = Sunset_Time.state tnow = Time.now # Determine POWER scene case tnow.wday when 5 # fri power_scene = "AllOn" when 6, 0 # sat, sun power_scene = "Weekend" else power_scene = "Workday" end power_scene = 'Night' if Time.now < (sunrise_time - '1 hours') || Time.now > (sunset_time + '5 hours') # Determine SETPOINT scene case mode when 'HEAT' setpoint_scene = 'Winter' when 'COOL' setpoint_scene = 'Summer' end #case Season_Name.state #when 'WINTER','AUTUMN' # setpoint_scene = 'Winter' #when 'SPRING','SUMMER' # setpoint_scene = 'Summer' #end [power_scene, setpoint_scene] end rule "hvac_startup" do on_start at_level: 80 run do for item, value in ITEM_DEFAULT_VALUES item.state = value if item.state.nil? end end end rule "hvac_scene_changed" do changed Airtouch_Unit_Fujitsu_Scene_Power changed Airtouch_Unit_Fujitsu_Scene_Setpoint run do deploy_scenes(Airtouch_Unit_Fujitsu_Scene_Power.state, Airtouch_Unit_Fujitsu_Scene_Setpoint.state) end end rule "hvac_event_off" do changed Airtouch_Unit_Fujitsu_Power, to: OFF run do shared_cache[:hvacstate] = OFF shared_cache[:hvacsolaroverride] = OFF logger.info "HVAC: was turned OFF" end end # Force the HVAC off at sunrise, assuming it was manually turned on beforehand. rule "hvac_sunrise_force_off" do channel "astro:sun:home:rise#event", attach: "sunrise" run do hvac_log "Turning unit off due to SUNRISE event" Airtouch_Unit_Fujitsu_Power.ensure.off end end #rule "hvac_morning_force_off" do # every :day, at: '07:00' # run do # hvac_log "Turning unit off due to reached 7am" # Airtouch_Unit_Fujitsu_Power.ensure.off # end #end rule "hvac_automation" do every 1.minutes run do |event| solar_remain = ForecastSolar_9Hens_Remaining.state inlet_temp = Airtouch_Unit_Fujitsu_Inlet_Temp.state max_temp_today = BOM_Day1_MaximumTemperature.state cur_temp = BOM_Day1_AirTemperature.state apparent_temp = BOM_Day1_ApparentTemperature.state # Use forecasts and inlet temp to determine if we should HEAT or COOL if max_temp_today <= 20 || apparent_temp <= 7 || (inlet_temp <= 12 | '°C') mode = 'HEAT' elsif max_temp_today >= 27 || apparent_temp >= 28 || (inlet_temp >= 28 | '°C') mode = 'COOL' else mode = nil end # Overrides for normal solar automation. Use inlet temp as gauge of general comfort level if (inlet_temp <= Hvac_Solar_Override_Trigger_Cool.state | '°C') || (inlet_temp >= Hvac_Solar_Override_Trigger_Heat.state | '°C') shared_cache[:hvacsolaroverride] = ON end if Airtouch_Unit_Fujitsu_Automation_Override.state == ON hvac_log "Manual override active" else if Airtouch_Unit_Fujitsu_Power.state == ON if mode.nil? hvac_log "Environmentals outside of automation bounds" if shared_cache[:hvacstate] == ON hvac_log "Temperatures (cur:#{cur_temp} inlet:#{inlet_temp} max:#{max_temp_today} app:#{apparent_temp}) outside of automation bounds" hvac_log "turning unit OFF due to automation state being ON" Airtouch_Unit_Fujitsu_Power.ensure.off end else hvac_log "Sunset = #{Sunset_Time.state}, Season = #{Season_Name.state}, Mode = #{mode}, SolarRemain = #{solar_remain}" if shared_cache[:hvacstate] == ON if shared_cache[:hvacsolaroverride] == ON if ((mode == 'COOL' && inlet_temp <= Hvac_Solar_Override_Normal_Heat.state | '°C') || (mode == 'HEAT' && inlet_temp >= Hvac_Solar_Override_Normal_Cool.state | '°C')) hvac_log "turn unit off NOW: SOLAR OVERRIDE IS ACTIVE AND INLET TEMP #{inlet_temp} IS NOW WITHIN COMFORTABLE RANGE" Airtouch_Unit_Fujitsu_Power.ensure.off end else sunset_avg_solar_power = Solar_Total_Gen_Power.average_since(DUSK_AVG_MINS.minutes.ago) if (solar_remain && (solar_remain < 3 | 'kWh')) || ((sunset_avg_solar_power < Hvac_Solar_Power_Threshold_Dusk.state | 'W') || (Time.now > Sunset_Time.state)) hvac_log "Solar energy (#{sunset_avg_solar_power} / 10min) too low OR we're past sunset time: TURN HVAC UNIT OFF NOW" Airtouch_Unit_Fujitsu_Power.ensure.off end end end end else if mode.nil? hvac_log "Temperatures (cur:#{cur_temp} inlet:#{inlet_temp} max:#{max_temp_today} app:#{apparent_temp}) outside of automation bounds" elsif shared_cache[:hvacsolaroverride] == ON || Time.now > Sunrise_Time.state sunrise_avg_solar_power = Solar_Total_Gen_Power.average_since(DAWN_AVG_MINS.minutes.ago) hvac_log "Sunrise = #{Sunrise_Time.state}, Season = #{Season_Name.state}, Mode = #{mode}, SolarGen(30mAVG) = #{sunrise_avg_solar_power}, SolarRemain = #{solar_remain}, Temp = cur:#{cur_temp} inlet:#{inlet_temp} max:#{max_temp_today} app:#{apparent_temp}" if shared_cache[:hvacsolaroverride] == ON || (sunrise_avg_solar_power > Hvac_Solar_Power_Threshold_Dawn.state | 'W') || ((sunrise_avg_solar_power > Hvac_Solar_Power_Threshold_Dawn.state | 'W') && solar_remain && (solar_remain > 20 | 'kWh')) if shared_cache[:hvacsolaroverride] == ON hvac_log "turn unit on NOW: SOLAR OVERRIDE IS ACTIVE BECAUSE INLET TEMP #{inlet_temp} IS OUT OF COMFORTABLE RANGE !!!" else hvac_log "turn unit on NOW: Average solar generation / solar forecast / temp forecast exceeded thresholds" end power_scene, setpoint_scene = determine_automation_scenes(mode) Airtouch_Unit_Fujitsu_Scene_Power.update(power_scene) Airtouch_Unit_Fujitsu_Scene_Setpoint.update(setpoint_scene) Airtouch_Unit_Fujitsu_Mode.ensure.command(mode) Airtouch_Unit_Fujitsu_Power.ensure.on hvac_log "Starting timer to set automation state" sleep 10 shared_cache[:hvacstate] = ON hvac_log "Mode is #{Airtouch_Unit_Fujitsu_Mode.state}, :hvacstate is #{shared_cache[:hvacstate]}, hvacsolaroverride is #{shared_cache[:hvacsolaroverride]}" end end end end end end