Skip to content
Lieven Hollevoet edited this page Mar 12, 2015 · 2 revisions

Internal Workings

An overview of the internal workings of MisterHouse.

High-Level Overview

Misterhouse runs by leveraging two sources of code. The first are standard Perl libraries that provide both object-oriented (OO) and non-OO functionality.

The second source of code are the code modules found in mh/code/common and typically one other place, a user's private code directory. The main mh/bin/mh executable reads in the currently selected user files (&::read_user_code) and parses them one at a time. If a line is believed to be involved in initialization or declare global variables, it is treated specially and removed from the code file, else the code is added to the current loop code.

If there are no errors found within the code modules, then misterhouse will evaluate the code loop several times per second. The parameters sleep_time and sleep_count control how often the loop code is evaluated by added delays into the loop code itself.

Thus the high level bin/mh (MisterHouse executable) pseudocode/logic would look like this:

  • parse the selected user files, treat initializations and declarations, add other code to the loop code
  • set the timers
  • do
  • wait until the sleep_time timer expires
  • execute the loop code
  • reset the sleep_time timer

Reading Selected User Files

From the MisterHouse FAQ, Question 1.14: how does mh read and use the user code in code_dir:

Each time mh is started or a Reload is done, mh will re-read all the .pl code members in the code_dir directory (this is a mh.ini parm) and the file data_dir/mh_temp.user_code is created. All of the objects and global variables (see FAQ question 2.4), are put at top of this file, and everything else is put in seperate subroutines, one for each file read. A &loop_code subroutine is also created (at the very bottom of mh_temp.user_code), that calls each member subroutine.

After creating this file, mh runs the perl eval function on its contents. This re-creates all the objects, global variables, and code_dir member subroutines. Then mh goes back into its normal loop where it queries various input data (e.g. serial, tcp/ip, voice, time), updates objects and variables, evals the &loop_code code (it does an eval, rather then execute it directly, so we can trap errors and not kill mh), sleeps for a bit, then repeats.

From this, the bin/mh (MisterHouse executable) pseudocode/logic looks like this:

  • read all the .pl code members in the code_dir directory (mh.ini parm)
  • parse this code and create data_dir/mh_temp.user_code
  • create the &loop_code subroutine at the bottom of data_dir/mh_temp.user_code
  • wait until the sleep_time timer expires
  • runs the perl eval function on data_dir/mh_temp.user_code
  • run the user code loop

User Code Loop

  • do
  • query various input data (e.g. serial, tcp/ip, voice, time)
  • update objects and variables
  • eval the &loop_code code
  • sleep for a bit

&loop_code subroutine

  • calls each member subroutine

sleep_time, sleep_count, User Code Loop Timing, And Benchmarks

There are two (well, actually one, but both are related) key .ini configuration parameters that have a direct impact on the number of loops per second that a machine will achieve: sleep_time and sleep_count.

sleep_time specifies the number of milliseconds that MisterHouse will sleep, i.e. do nothing, after executing one pass of all user code, i.e. "the loop". The default, if no sleep_time is specified in a .ini file, is hardcoded to 50 milliseconds in the mh executable.

The user (loop) code is actually not executed all at once. Instead, sleep_time is divided by sleep_count, and sleep_time/sleep_count sleep slots are spread out through all enabled user code modules. The reason for this, based on comments in write_user_code(), is:

  • Add sleeps 1/$i-th way through user code.
  • Improves cache usage for less %cpu than 1 big sleep.

You can see the interlaced sleep_time() calls in loop_code(), at the bottom of mh_temp.user_code in your data directory.

With the default of 50 milliseconds of sleep_time, and assuming a fast machine that takes close to 0 milliseconds to execute all user code (just for illustration purposes), the number of loops per seconds will be:

1/50 milliseconds = 20 loops per second

This explains why many people see a number of loops per second that is close to 20. So, when running with default parameters on a fast machine, execution speed is being throttled, and the execution speed could be made to go faster by tweaking the sleep_time parameter, but there is usually not a need for that.

The number of loops per second can be determined in several ways:

  • Web interface: the current number of loops per second is stored in the global variable $Info{loop_speed}. The value of this variable can be obtained via the web interface: Mr House Home -> Browse Widgets -> Global Vars -> look for $Info{loop_speed}.
  • Via the Tk interface: look for the "Loops Per Second Current/Max" widget. This requires some Tk-related user code modules to be enabled and some .ini configuration parameter to be set.
  • Enable the benchmarks.pl common user code file and use the voice command "what is your normal speed". This action can be triggered from the ia5 web interface too (MisterHouse Home > About MisterHouse > What is your normal speed).

Benchmarking

The other commands in common user code file benchmarks.pl are useful since they allow to obtain an idea of how well your user code is performing:

The voice command "what is your max speed" temporarily sets sleep_time to zero (actually, the internal variable that is used, $Loop_Sleep_Time), and then executes 3 seconds worth of user code. Since there is no sleeping between user code passes, we get the theoretical maximum number of loops per second we can get. In my case, I get (on a Pentium 4 running at 3.2 GHz):

100% of cpu, 638 loops/sec, and 39 megabytes

The last two interesting commands in common user code file benchmarks.pl are "start a by name speed benchmark" and "start a by speed speed benchmark". According to the set_info() information for these commands, they "[...] will suspend normal mh while it benchmarks each code member individually. It can take few minutes."

Running these speed tests will result in a benchmark.log file in your log directory that looks like:

Benchmark report.  Loop count=367078.  The following is in milliseconds
   OTHER                 avg= 2.15 total=787664 |   monitor_battery_devices avg= 0.01 total= 4081
   USER                  avg= 2.18 total=800593 |   monitor_occupancy      avg= 0.14 total=51614
  asterisk               avg= 0.02 total= 6394  |   phone                  avg= 0.01 total= 4352
  benchmarks             avg= 0.05 total=19772  |   phone_logs             avg= 0.03 total=11848
  comic_dailystrips      avg= 0.01 total= 4855  |   read_temps             avg= 0.01 total= 4357
  eliza_server           avg= 0.04 total=14741  |   speak_chime            avg= 0.01 total= 2693
  holiday                avg= 0.01 total= 3842  |   telnet                 avg= 0.07 total=25521
  insteon_glue           avg= 0.02 total= 5630  |   time_info              avg= 0.01 total= 2790
  insteon_status         avg= 0.44 total=161468 |   timers                 avg= 0.04 total=13994
  insteon_table          avg= 0.01 total= 2890  |   tk_frames              avg= 0.01 total= 3097
  internet_weather       avg= 0.03 total=10912  |   tk_widgets             avg= 0.01 total= 2656
  iphone                 avg= 0.01 total= 2702  |   triggers_table         avg= 0.03 total=10940
  light_control          avg= 0.08 total=29336  |   trivia                 avg= 0.02 total= 5626
  menu                   avg= 0.01 total= 3114  |   tv_grid                avg= 0.04 total=14004
  mh_control             avg= 0.04 total=15079  |   tv_info                avg= 0.04 total=13664
  mh_sound               avg= 0.08 total=28063  |   weather_rrd_update     avg= 0.02 total= 7928
  mhsend_server          avg= 0.02 total= 5897  |   x10_item_commands      avg= 0.01 total= 2617
  monitor_battery_devices avg= 0.01 total= 4081 |

The difference between "a by name" speed test and "a by speed" speed test is how the output is sorted, i.e. by user code file name versus time each user code file takes to run.


Notes:

  • processes in bold/italic will be detailed later on
Clone this wiki locally