-
Notifications
You must be signed in to change notification settings - Fork 131
InternalWorkings
An overview of the internal workings of MisterHouse.
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 eval
uate the code loop several times per second. The parameters sleep_time
and sleep_count
control how often the loop code is eval
uated 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
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 ofmh_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
- 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
- calls each member subroutine
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).
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