:- ensure_loaded('$REGULUS/PrologLib/compatibility').
:- module(start,
[start_processes/2,
test_start/1]
).
%------------------------------------------------------------------------------------
:- use_module('$REGULUS/PrologLib/utilities').
:- use_module(library(process)).
:- use_module(library(system)).
:- use_module(library(lists)).
:- use_module(library(xml)).
:- use_module(library(file_systems)).
/*
start_processes(+XMLFile, +MachineID).
Tool for starting multiple processes, each of which may need to wait for
trace input from earlier processes. Execution is controlled by an XML config file.
One call is made on each machine. The different instantiations of the process
communicate by means of touchfiles, which must be placed in an area
accessible to all the machines.
Example of call:
start_processes('$REGULUS/tmp/test_start.xml', machine1).
Example of XML config file:
$REGULUS/tmp/starttouchfiles
$REGULUS/tmp/starttracefiles
$REGULUS/tmp/startstatus.html
process1
machine1
sicstus -l $REGULUS/PrologLib/test_start_a.pl
process1.txt
process2
machine1
sicstus -l $REGULUS/PrologLib/test_start_b.pl
a: 5
process2.txt
*/
%------------------------------------------------------------------------------------
test_start(1) :-
start_processes('$REGULUS/tmp/test_start.xml', machine1).
%----------------------------------------------------
start_processes(ConfigFile, MachineId) :-
read_config_file(ConfigFile, Config),
init_start_processes(Config, MachineId, Plan),
execute_start_processes(Plan, ProcessIds),
watcher_loop(ProcessIds).
%----------------------------------------------------
/*
% Internalize the XML file
Typical input:
$REGULUS/tmp/starttouchfiles
$REGULUS/tmp/starttracefiles
$REGULUS/tmp/startstatus.html
process1
machine1
sicstus -l $REGULUS/PrologLib/test_start_a.pl
process1.txt
process2
machine1
sicstus -l $REGULUS/PrologLib/test_start_b.pl
a: 5
process2.txt
Output:
[declarations([(touchfiledir='$REGULUS/tmp/starttouchfiles'),
(tracefiledir='$REGULUS/tmp/starttracefiles'),
(webpage='$REGULUS/tmp/startstatus.html')]),
process([(id=process1), (machine=machine1),
(command='sicstus -l $REGULUS/PrologLib/test_start_a.pl'),
(tracefile='process1.txt')]),
process([(id=process2), (machine=machine1),
(command='sicstus -l $REGULUS/PrologLib/test_start_b.pl'),
(condition=[process=process1,string='a: 5']),
(tracefile='process2.txt')])]
*/
read_config_file(File, Config) :-
safe_absolute_file_name(File, AbsFile),
read_file_to_string(File, String),
( xml_parse(String, Config0) ->
replace_strings_with_atoms_in_xml(Config0, Config1),
remove_comments_in_xml(Config1, Config2),
read_config_file1(Config2, Config),
format('~N--- Read config file: ~w~n', [AbsFile])
;
otherwise ->
format('~N*** Error: unable to parse XML~n', [])
),
!.
read_config_file(File, _Config) :-
format('~N*** Error: unable to read config file: ~w~n', [File]),
fail.
read_config_file1(XML, Config) :-
( XML = xml(_Version, [element(start, _, Body)]) ->
read_config_file2(Body, Config)
;
otherwise ->
format('~N*** Error: outermost tag in file must be ~n', []),
fail
).
read_config_file2([], []).
read_config_file2([F | R], [F1 | R1]) :-
read_config_file_element(F, F1),
!,
read_config_file2(R, R1).
read_config_file_element(element(declarations, _, Body), declarations(Declarations)) :-
!,
read_declarations(Body, Declarations).
read_config_file_element(element(process, _, Body), process(Process)) :-
!,
read_process(Body, Process).
read_config_file_element(element(Tag, _, _), _) :-
format('~N*** Error: unknown tag <~w>~n', [Tag]),
fail.
read_declarations(Body, Declarations) :-
read_declarations1(Body, Declarations).
read_declarations1([], []).
read_declarations1([F | R], [F1 | R1]) :-
read_declaration_element(F, F1),
!,
read_declarations1(R, R1).
read_declaration_element(element(Tag, _, Body), Tag=Value) :-
( ( member(Tag, [touchfiledir, tracefiledir, webpage]), Body = [Value] ) ->
true
;
otherwise ->
format('~N*** Error: bad tag <~w> in declarations~n', [Tag]),
fail
).
read_process(Body, Process) :-
read_process1(Body, Process).
read_process1([], []).
read_process1([F | R], [F1 | R1]) :-
read_process_element(F, F1),
!,
read_process1(R, R1).
read_process_element(element(Tag, Attr, Body), Result) :-
( ( member(Tag, [id, machine, command, tracefile]), Body = [Value], Attr = [] ) ->
Result = ( Tag=Value )
;
( Tag = condition, Body = [String], Attr = [process = Process] ) ->
Result = ( condition = [ process = Process, string = String ] )
;
otherwise ->
format('~N*** Error: bad tag <~w> in process~n', [Tag]),
fail
).
%----------------------------------------------------
/*
Set things up: initialize the directories to use, create the plan, the Bash scripts and web page.
Typical input:
[declarations([(touchfiledir='$REGULUS/tmp/starttouchfiles'),
(tracefiledir='$REGULUS/tmp/starttracefiles'),
(webpage='$REGULUS/tmp/startstatus.html')]),
process([(id=process1), (machine=machine1),
(command='sicstus -l $REGULUS/PrologLib/test_start_a.pl'),
(tracefile='process1.txt')]),
process([(id=process2), (machine=machine1),
(command='sicstus -l $REGULUS/PrologLib/test_start_b.pl'),
(condition=[process=process1,string='a: 5']),
(tracefile='process2.txt')])]
Typical output plan:
[process([(id=process1),
(machine=machine1),
(command='sicstus -l $REGULUS/PrologLib/test_start_a.pl'),
(tracefile = 'c:/cygwin64/home/speech/regulus-code/trunk/regulus/tmp/starttracefiles/process1.txt'),
(string_conditions = [('a: 5'-'$REGULUS/tmp/starttouchfiles/process1_found_a__5.txt')]),
(script = 'c:/cygwin64/home/speech/regulus-code/trunk/regulus/tmp/starttouchfiles/run_process1')]),
process([(id=process2),
(machine=machine1),
(command='sicstus -l $REGULUS/PrologLib/test_start_b.pl'),
(touchfile_condition = '$REGULUS/tmp/starttouchfiles/process1_found_a__5.txt'),
(tracefile = 'c:/cygwin64/home/speech/regulus-code/trunk/regulus/tmp/starttracefiles/process2.txt'),
(string_conditions=[]),
(script = 'c:/cygwin64/home/speech/regulus-code/trunk/regulus/tmp/starttouchfiles/run_process2')])]
Typical Bash file:
#!/bin/bash
sicstus -l $REGULUS/PrologLib/test_start_a.pl |& sicstus -l $REGULUS/PrologLib/search_for_patterns.pl --goal "search_for_patterns(['a: 5'-'$REGULUS/tmp/starttouchfiles/process1_found_a__5.txt'])." > c:/cygwin64/home/speech/regulus-code/trunk/regulus/tmp/starttracefiles/process1.txt
Typical web page:
Trace for process1
Trace for process2
*/
init_start_processes(Config, MachineId, Plan1) :-
format('~N--- Initialising plan~n', []),
get_declarations_from_config(Config, Declarations, Config1),
init_directories(Declarations),
config_to_plan(Config1, MachineId, Declarations, Plan),
create_bash_scripts(Plan, Declarations, Plan1),
create_web_page(Plan1, Declarations),
init_touchfiles_and_tracefiles(Plan1),
format('~N--- Plan initialized~n', []),
!.
init_start_processes(_Config, _MachineId, _Plan) :-
format('~N*** Error: unable to initialise start script~n', []),
fail.
get_declarations_from_config(Config, Declarations, Config1) :-
get_declarations_from_config1(Config, DeclarationsList, Config1),
( DeclarationsList = [Declarations] ->
check_declarations(Declarations)
;
DeclarationsList = [] ->
format('~N*** Error: no tag in start script~n', []),
fail
;
otherwise ->
format('~N*** Error: multiple tags in start script~n', []),
fail
).
get_declarations_from_config1([], [], []).
get_declarations_from_config1([declarations(List) | R], [declarations(List) | R1], R2) :-
!,
get_declarations_from_config1(R, R1, R2).
get_declarations_from_config1([Other | R], R1, [Other | R2]) :-
!,
get_declarations_from_config1(R, R1, R2).
check_declarations(declarations(List)) :-
complain_if_missing_tag(touchfiledir, List),
complain_if_missing_tag(tracefiledir, List),
complain_if_missing_tag(webpage, List),
!.
check_declarations(_Other) :-
format('~N*** Error: bad tag in start script~n', []),
fail.
% Create any directories that may be needed
init_directories(declarations(List)) :-
member(touchfiledir=Touchfiledir, List),
member(tracefiledir=Tracefiledir, List),
create_directories_as_required([Touchfiledir, Tracefiledir]),
!.
init_directories(_Declarations) :-
format('~N*** Error: unable to initialise directories~n', []),
fail.
create_directories_as_required([]).
create_directories_as_required([F | R]) :-
create_directory_if_necessary(F),
create_directory_above_if_necessary(F),
!,
create_directories_as_required(R).
% Reformulate conditions in terms of creating and waiting for touchfiles.
config_to_plan(Processes, MachineId, Declarations, Plan) :-
config_to_plan1(Processes, Declarations, Processes1, []-Conditions),
config_to_plan2(Processes1, MachineId, Declarations, Conditions, Plan),
!.
config_to_plan(_Processes, _MachineId, _Declarations, _Plan) :-
format('~N*** Error: unable to convert to plan~n', []),
fail.
config_to_plan1([], _Declarations, [], CondsIn-CondsIn).
config_to_plan1([F | R], Declarations, [F1 | R1], CondsIn-CondsOut) :-
config_item_to_plan1(F, Declarations, F1, CondsIn-CondsNext),
!,
config_to_plan1(R, Declarations, R1, CondsNext-CondsOut).
config_item_to_plan1(process(List), Declarations, process(List1), CondsIn-CondsOut) :-
complain_if_missing_tag(id, List),
complain_if_missing_tag(machine, List),
complain_if_missing_tag(command, List),
complain_if_missing_tag(tracefile, List),
config_item_to_plan1_sub(List, Declarations, List1, CondsIn-CondsOut).
config_item_to_plan1_sub([], _Declarations, [], CondsIn-CondsIn).
config_item_to_plan1_sub([F | R], Declarations, [F1 | R1], CondsIn-CondsOut) :-
config_item_to_plan1_sub1(F, Declarations, F1, CondsIn-CondsNext),
config_item_to_plan1_sub(R, Declarations, R1, CondsNext-CondsOut).
config_item_to_plan1_sub1(condition=[process=PreId, string=Str], Declarations, F1, CondsIn-[Cond | CondsIn]) :-
get_touchfile_name(PreId, Str, Declarations, Touchfile),
F1 = ( touchfile_condition = Touchfile ),
Cond = condition([process=PreId, string=Str, touchfile=Touchfile]),
!.
config_item_to_plan1_sub1(tracefile=File, Declarations, tracefile=AbsFullFile, CondsIn-CondsIn) :-
get_tracefile_dir(Declarations, Dir),
format_to_atom('~w/~w', [Dir, File], FullFile),
safe_absolute_file_name(FullFile, AbsFullFile),
!.
config_item_to_plan1_sub1(Other, _Declarations, Other, CondsIn-CondsIn).
get_touchfile_name(ProcessId, Str, Declarations, Touchfile) :-
get_touchfile_dir(Declarations, Dir),
clean_atom_for_use_in_filename(Str, Str1),
format_to_atom('~w/~w_found_~w.txt', [Dir, ProcessId, Str1], Touchfile),
!.
config_to_plan2([], _MachineId, _Declarations, _Conditions, []).
config_to_plan2([F | R], MachineId, Declarations, Conditions, [F1 | R1]) :-
config_item_to_plan2(F, MachineId, Declarations, Conditions, F1),
!,
config_to_plan2(R, MachineId, Declarations, Conditions, R1).
config_to_plan2([_F | R], MachineId, Declarations, Conditions, R1) :-
!,
config_to_plan2(R, MachineId, Declarations, Conditions, R1).
config_item_to_plan2(process(List), MachineId, _Declarations, _Conditions, _Output) :-
\+ member(machine=MachineId, List),
!,
fail.
config_item_to_plan2(process(List), _MachineId, _Declarations, Conditions, process(List1)) :-
member(id=ProcessId, List),
findall(StrAtom-Touchfile,
( member(condition(CondList), Conditions),
member(process=ProcessId, CondList),
member(string=StrAtom, CondList),
member(touchfile=Touchfile, CondList)
%atom_codes(StrAtom, Str)
),
StringConditions),
append(List, [string_conditions=StringConditions], List1),
!.
config_item_to_plan2(F, MachineId, Declarations, Conditions, F1) :-
format('~N*** Error: bad call: ~w~n',
[config_item_to_plan2(F, MachineId, Declarations, Conditions, F1)]),
fail.
% Create a Bash script for each process.
create_bash_scripts(Plan, Declarations, Plan1) :-
create_bash_scripts1(Plan, Declarations, Plan1),
format('~N--- Created bash scripts~n', []).
create_bash_scripts1([], _Declarations, []).
create_bash_scripts1([F | R], Declarations, [F1 | R1]) :-
create_bash_script_for_plan_element(F, Declarations, F1),
!,
create_bash_scripts1(R, Declarations, R1).
create_bash_script_for_plan_element(process(List), Declarations, process(List1)) :-
member(id=ProcessId, List),
member(command=Command, List),
member(tracefile=Tracefile, List),
member(string_conditions=Alist, List),
get_scriptfile_name(ProcessId, Declarations, ScriptFile),
open(ScriptFile, write, S),
format(S, '#!/bin/bash~n', []),
format(S,
'~w |& sicstus -l $REGULUS/PrologLib/search_for_patterns.pl --goal "~q." > ~w',
[Command, search_for_patterns(Alist), Tracefile]),
close(S),
format('~NWritten script file: ~w~n', [ScriptFile]),
append(List, [script=ScriptFile], List1),
!.
create_bash_script_for_plan_element(F, Declarations, F1) :-
format('~N*** Error: bad call: ~w~n',
[create_bash_script_for_plan_element(F, Declarations, F1)]),
fail.
get_scriptfile_name(ProcessId, Declarations, AbsScriptFile) :-
get_touchfile_dir(Declarations, Dir),
format_to_atom('~w/run_~w', [Dir, ProcessId], ScriptFile),
safe_absolute_file_name(ScriptFile, AbsScriptFile),
!.
get_scriptfile_name(ProcessId, Declarations, AbsScriptFile) :-
format('~N*** Error: bad call: ~w~n',
[get_scriptfile_name(ProcessId, Declarations, AbsScriptFile)]),
fail.
% Create a web page that we can use to track output
create_web_page(Plan, Declarations) :-
get_web_page(Declarations, File),
safe_absolute_file_name(File, AbsFile),
open(AbsFile, write, S),
write_web_page_intro(S),
write_web_page_body(Plan, S),
write_web_page_coda(S),
close(S),
format('~N--- Written out web page ~w~n', [AbsFile]),
!.
create_web_page(Plan, Declarations) :-
format('~N*** Error: bad call: ~w~n',
[create_web_page(Plan, Declarations)]),
fail.
write_web_page_intro(S) :-
format(S, '~N~n', []),
format(S, '~N~n~n', []),
!.
write_web_page_body([], _S).
write_web_page_body([F | R], S) :-
write_web_page_body_element(F, S),
!,
write_web_page_body(R, S).
write_web_page_body_element(process(List), S) :-
member(tracefile=File, List),
member(id=Id, List),
safe_absolute_file_name(File, AbsFile),
format(S, '~NTrace for ~w
~n~n', [AbsFile, Id]),
!.
write_web_page_coda(S) :-
format(S, '~N~n', []),
format(S, '~N~n', []),
!.
% Delete old touchfiles, overwrite tracefiles with null content
init_touchfiles_and_tracefiles(Plan) :-
init_touchfiles_and_tracefiles1(Plan),
format('~N--- Removed old touchfiles~n', []).
init_touchfiles_and_tracefiles1([]).
init_touchfiles_and_tracefiles1([F | R]) :-
init_touchfiles_and_tracefiles_for_plan_element(F),
!,
init_touchfiles_and_tracefiles1(R).
init_touchfiles_and_tracefiles_for_plan_element(process(List)) :-
init_touchfile_for_plan_element(List),
init_tracefile_for_plan_element(List),
!.
init_touchfiles_and_tracefiles_for_plan_element(_Other).
init_touchfile_for_plan_element(List) :-
member(touchfile_condition=Touchfile, List),
safe_absolute_file_name(Touchfile, AbsTouchfile),
safe_file_exists(AbsTouchfile),
delete_file(AbsTouchfile),
!.
init_touchfile_for_plan_element(_List).
init_tracefile_for_plan_element(List) :-
member(tracefile=Tracefile, List),
write_atom_list_to_file(['Waiting for trace output'], Tracefile),
!.
init_tracefile_for_plan_element(_List).
%====================================================
% Run-time
execute_start_processes(Plan, ProcessIds) :-
execute_start_processes1(Plan, ProcessIds),
format('~N--- All processes started~n', []),
!.
execute_start_processes(Plan, _ProcessIds) :-
format('~N*** Error: bad attempt to run plan: ~w~n', []),
prettyprint(Plan),
fail.
execute_start_processes1([], []).
execute_start_processes1([F | R], [F1 | R1]) :-
execute_start_process_element(F, F1),
!,
execute_start_processes1(R, R1).
execute_start_process_element(process(List), ProcessId) :-
member(id=Id, List),
member(script=Script, List),
format('~N--- Process: ~w~n', [Id]),
wait_for_touchfile_conditions(List),
absolute_file_name(path('bash.exe'), Bash),
process_create(Bash, [file(Script)], [process(ProcessId)]),
format('~N--- Process started~n', []),
!.
wait_for_touchfile_conditions([]).
wait_for_touchfile_conditions([F | R]) :-
wait_for_touchfile_condition(F),
!,
wait_for_touchfile_conditions(R).
wait_for_touchfile_condition(touchfile_condition=Touchfile) :-
keep_checking_for_touchfile(Touchfile),
!.
wait_for_touchfile_condition(_Other).
keep_checking_for_touchfile(File) :-
safe_absolute_file_name(File, AbsFile),
format('~NWaiting for file ~w~n', [AbsFile]),
keep_checking_for_touchfile1(AbsFile).
keep_checking_for_touchfile1(File) :-
( safe_file_exists(File) ->
format('~NFound file ~w~n', [File])
;
otherwise ->
%format('~NUnable to find file ~w, waiting...~n', [File]),
sleep(1),
!,
keep_checking_for_touchfile1(File)
).
%----------------------------------------------------
% This will later contain more commands.
watcher_loop(ProcessIds) :-
format('~N==========================================================~n~n', []),
show_process_ids(ProcessIds),
get_loop_prompt(PromptAtom),
format(PromptAtom, []),
read_line(Line),
split_string_into_words(Line, Words0),
lowercase_atom_list(Words0, Words),
( member(Words, [[kill]]) ->
handle_top_loop_command(Line, Words, ProcessIds)
;
otherwise ->
handle_top_loop_command(Line, Words),
!,
watcher_loop(ProcessIds)
).
show_process_ids(ProcessIds) :-
findall(PID,
( member(ProcessId, ProcessIds),
process_id(ProcessId, PID)
),
PIDs),
format('~NPIDS: ~w~n~n', [PIDs]).
get_loop_prompt(PromptAtom) :-
PromptAtom = '~NType "kill" to kill all processes >> ',
!.
handle_top_loop_command(Line, Words, ProcessIds) :-
( member(Words, [[kill]]) ->
kill_process_list(ProcessIds)
;
otherwise ->
format('~NUnknown command: "~s"~n', [Line])
).
kill_process_list([]).
kill_process_list([F | R]) :-
try_to_kill_process(F),
!,
kill_process_list(R).
%try_to_kill_process(Process) :-
% process_kill(Process),
% !.
try_to_kill_process(Process) :-
process_id(Process, PID),
%system_on_list(['kill -f', PID]),
%system_on_list(['kill -f -t', PID]),
system_on_list(['taskkill -f -t -pid', PID]),
!.
try_to_kill_process(Process) :-
format('~N*** Error: bad call: ~w~n', [try_to_kill_process(Process)]),
fail.
%====================================================
% Utilities
get_touchfile_dir(declarations(List), Dir) :-
member(touchfiledir=Dir, List),
!.
get_touchfile_dir(Other, Dir) :-
format('~N*** Error: bad call: ~w~n',
[get_touchfile_dir(Other, Dir)]),
fail.
get_tracefile_dir(declarations(List), Dir) :-
member(tracefiledir=Dir, List),
!.
get_tracefile_dir(Other, Dir) :-
format('~N*** Error: bad call: ~w~n',
[get_tracefile_dir(Other, Dir)]),
fail.
get_web_page(declarations(List), File) :-
member(webpage=File, List),
!.
get_web_page(Other, File) :-
format('~N*** Error: bad call: ~w~n',
[get_web_page(Other, File)]),
fail.
complain_if_missing_tag(Tag, List) :-
( member(Tag=_, List) ->
true
;
otherwise ->
format('~N*** Error: tag ~w not found~n', [Tag]),
fail
).
clean_atom_for_use_in_filename(Atom, Atom1) :-
atomic(Atom),
atom_codes(Atom, Str),
clean_string_for_use_in_filename(Str, Str1),
atom_codes(Atom1, Str1),
!.
clean_atom_for_use_in_filename(Atom, Atom1) :-
format('~N*** Error: bad call: ~w~n', [clean_atom_for_use_in_filename(Atom, Atom1)]),
fail.
clean_string_for_use_in_filename([], []).
clean_string_for_use_in_filename([F | R], [F1 | R1]) :-
( ok_char_for_filename(F) ->
F1 = F
;
otherwise ->
F1 = 0'_
),
clean_string_for_use_in_filename(R, R1).
ok_char_for_filename(Char) :-
lowercase_char(Char),
!.
ok_char_for_filename(Char) :-
uppercase_char(Char),
!.
ok_char_for_filename(Char) :-
digit_char(Char),
!.
ok_char_for_filename(Char) :-
member(Char, "_-"),
!.