372 lines
11 KiB
Erlang
372 lines
11 KiB
Erlang
%%% @doc
|
|
%%% GajuMine Controller
|
|
%%% @end
|
|
|
|
-module(gmc_con).
|
|
-vsn("0.2.0").
|
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
|
-copyright("Craig Everett <craigeverett@qpq.swiss>").
|
|
-license("GPL-3.0-or-later").
|
|
|
|
-behavior(gen_server).
|
|
-export([start_stop/0, gajudesk/0]).
|
|
-export([conf/0, conf/1]).
|
|
-export([bin_dir/0]).
|
|
-export([network/0]).
|
|
-export([start_link/0, stop/0]).
|
|
-export([init/1, terminate/2, code_change/3,
|
|
handle_call/3, handle_cast/2, handle_info/2]).
|
|
-include("$zx_include/zx_logger.hrl").
|
|
-include("$gajudesk_include/gd.hrl").
|
|
|
|
|
|
%%% Type and Record Definitions
|
|
|
|
|
|
-record(s,
|
|
{version = 1 :: integer(),
|
|
window = none :: none | wx:wx_object(),
|
|
network = <<"mainnet">> :: binary(), % <<"testnet">> | <<"mainnet">>
|
|
exec_dir = platform_dir() :: file:filename(),
|
|
acc = none :: none | binary(),
|
|
keys = [] :: [],
|
|
max_cores = 2 :: pos_integer(),
|
|
max_mem = 3550722201 :: pos_integer(),
|
|
prefs = #{} :: #{},
|
|
gmc_conf = none :: none | reference()}).
|
|
|
|
-type state() :: #s{}.
|
|
|
|
|
|
|
|
%% Interface
|
|
|
|
-spec start_stop() -> ok.
|
|
|
|
start_stop() ->
|
|
gen_server:cast(?MODULE, start_stop).
|
|
|
|
|
|
-spec gajudesk() -> ok.
|
|
|
|
gajudesk() ->
|
|
gen_server:cast(?MODULE, gajudesk).
|
|
|
|
|
|
-spec conf() -> ok.
|
|
|
|
conf() ->
|
|
gen_server:cast(?MODULE, conf).
|
|
|
|
|
|
-spec conf({Account, Keys, Network, MaxCores, MaxMem}) -> ok
|
|
when Account :: none | binary(), % <<"ak_...">>
|
|
Keys :: [binary()], % [<<"ak_...">>]
|
|
Network :: binary(), % <<"mainnet">> | <<"testnet">> | <<"devnet">>
|
|
MaxCores :: none | integer(),
|
|
MaxMem :: none | integer().
|
|
|
|
conf(Info) ->
|
|
gen_server:cast(?MODULE, {conf, Info}).
|
|
|
|
|
|
-spec bin_dir() -> file:filename().
|
|
|
|
bin_dir() ->
|
|
gen_server:call(?MODULE, bin_dir).
|
|
|
|
|
|
-spec network() -> mainnet | testnet | none.
|
|
|
|
network() ->
|
|
gen_server:call(?MODULE, network).
|
|
|
|
|
|
-spec stop() -> ok.
|
|
|
|
stop() ->
|
|
gen_server:cast(?MODULE, stop).
|
|
|
|
|
|
|
|
%%% Startup Functions
|
|
|
|
|
|
-spec start_link() -> Result
|
|
when Result :: {ok, pid()}
|
|
| {error, Reason},
|
|
Reason :: {already_started, pid()}
|
|
| {shutdown, term()}
|
|
| term().
|
|
%% @private
|
|
%% Called by gmc_sup.
|
|
|
|
start_link() ->
|
|
gen_server:start_link({local, ?MODULE}, ?MODULE, none, []).
|
|
|
|
|
|
-spec init(none) -> {ok, state()}.
|
|
|
|
init(none) ->
|
|
ok = log(info, "Starting"),
|
|
_ = process_flag(sensitive, true),
|
|
{Acc, Keys, Network, MaxCores, MaxMem} =
|
|
case read_conf() of
|
|
{ok, C} ->
|
|
{maps:get(account, C, none),
|
|
maps:get(keys, C, []),
|
|
maps:get(network, C, <<"mainnet">>),
|
|
maps:get(max_cores, C, 2),
|
|
maps:get(max_mem, C, 3550722201)};
|
|
none ->
|
|
{none, [], <<"mainnet">>, 2, 3550722201}
|
|
end,
|
|
State = #s{network = Network, acc = Acc, keys = Keys, max_cores = MaxCores, max_mem = MaxMem},
|
|
{ok, start_gui(State)}.
|
|
|
|
|
|
platform_dir() ->
|
|
#{deps := Deps} = zx_daemon:meta(),
|
|
AppID = lists:keyfind("cuckoo_cpu", 2, Deps),
|
|
Priv = filename:join(zx_lib:ppath(lib, AppID), "priv"),
|
|
Dir =
|
|
case os:type() of
|
|
{unix, linux} ->
|
|
"linux_x86_64";
|
|
{unix, darwin} ->
|
|
case erlang:system_info(system_architecture) of
|
|
"aarch64-" ++ _ ->
|
|
"mac_m2";
|
|
Arch ->
|
|
ok = log(info, "system_architecture: ~p", [Arch]),
|
|
"mac_x86_64"
|
|
end;
|
|
{win32, nt} ->
|
|
"win_x86_64"
|
|
end,
|
|
filename:join(Priv, Dir).
|
|
|
|
|
|
start_gui(State = #s{acc = none}) ->
|
|
Window = gmc_gui:start_link(#{}),
|
|
ok = gmc_gui:ask_conf(),
|
|
State#s{window = Window};
|
|
start_gui(State) ->
|
|
Window = gmc_gui:start_link(#{}),
|
|
State#s{window = Window}.
|
|
|
|
|
|
%%% gen_server Message Handling Callbacks
|
|
|
|
|
|
handle_call(network, _, State = #s{network = Network}) ->
|
|
{reply, Network, State};
|
|
handle_call(bin_dir, _, State = #s{exec_dir = BinDir}) ->
|
|
{reply, BinDir, State};
|
|
handle_call(Unexpected, From, State) ->
|
|
ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]),
|
|
{noreply, State}.
|
|
|
|
|
|
handle_cast(start_stop, State) ->
|
|
ok = do_start_stop(State),
|
|
{noreply, State};
|
|
handle_cast(gajudesk, State) ->
|
|
ok = do_gajudesk(),
|
|
{noreply, State};
|
|
handle_cast(conf, State) ->
|
|
NewState = run_gmc_conf(State),
|
|
{noreply, NewState};
|
|
handle_cast({conf, Info}, State) ->
|
|
NewState = do_conf(Info, State),
|
|
{noreply, NewState};
|
|
handle_cast(stop, State) ->
|
|
ok = do_stop(),
|
|
ok = log(info, "Received a 'stop' message."),
|
|
{noreply, State};
|
|
handle_cast(Unexpected, State) ->
|
|
ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]),
|
|
{noreply, State}.
|
|
|
|
|
|
handle_info({gproc_ps_event, Event, Data}, State) ->
|
|
ok = gmc_gui:message({Event, Data}),
|
|
{noreply, State};
|
|
handle_info({'DOWN', Mon, process, PID, Info}, State = #s{gmc_conf = Mon}) ->
|
|
ok = log(info, "gmc_conf ~p closed with ~p", [PID, Info]),
|
|
NewState = State#s{gmc_conf = none},
|
|
{noreply, NewState};
|
|
handle_info(Unexpected, State) ->
|
|
ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]),
|
|
{noreply, State}.
|
|
|
|
|
|
code_change(_, State, _) ->
|
|
{ok, State}.
|
|
|
|
|
|
terminate(Reason, State) ->
|
|
ok = log(info, "Reason: ~p,", [Reason]),
|
|
ok = persist(State),
|
|
case whereis(gd_con) of
|
|
undefined ->
|
|
zx:stop();
|
|
PID ->
|
|
ok = log(info, "gd_con found at: ~p", [PID]),
|
|
application:stop(gajumine)
|
|
end.
|
|
|
|
|
|
do_stop() ->
|
|
case is_pid(whereis(gd_con)) of
|
|
false -> zx:stop();
|
|
true -> application:stop(gajumine)
|
|
end.
|
|
|
|
|
|
%%% Doers
|
|
|
|
do_start_stop(#s{acc = PubKey, network = Network, max_mem = MaxMem}) ->
|
|
% smrt guy stuff happens here
|
|
{Bits, Eureka} =
|
|
case Network of
|
|
<<"mainnet">> -> {"29", <<"https://gajumining.com/api/workers/", PubKey/binary>>};
|
|
<<"testnet">> -> {"15", <<"https://test.gajumining.com/api/workers/", PubKey/binary>>}
|
|
end,
|
|
{Fatness, Type} =
|
|
case os:type() of
|
|
{unix, linux} ->
|
|
case Network of
|
|
<<"mainnet">> -> {"mean", "avx2"};
|
|
<<"testnet">> -> {"mean", "generic"}
|
|
end;
|
|
{unix, darwin} ->
|
|
% Check memory. >7gb gets mean, <7gb gets lean
|
|
{"mean", "generic"};
|
|
{win32, nt} ->
|
|
% Check memory. >7gb gets mean, <7gb gets lean
|
|
% Check avx2.
|
|
% Both should be provided by the F# start program
|
|
case Network of
|
|
<<"mainnet">> -> {"mean", "avx2.exe"};
|
|
<<"testnet">> -> {"mean", "generic.exe"}
|
|
end
|
|
end,
|
|
Miner = unicode:characters_to_binary([Fatness, Bits, "-", Type]),
|
|
Count = optimize_count(MaxMem),
|
|
Instance = #{<<"executable">> => Miner},
|
|
Workers = lists:duplicate(Count, Instance),
|
|
Profile =
|
|
[{pubkey, PubKey},
|
|
{workers, Workers},
|
|
{pool_admin_url, Eureka}],
|
|
ok = gmc_gui:message({notice, "Starting..."}),
|
|
ok = gmc_gui:message({notice, ["Miner: ", Miner]}),
|
|
{ok, Apps} = gmhc_app:start(Profile),
|
|
Started = io_lib:format("Apps started: ~p", [Apps]),
|
|
ok = log(info, Started),
|
|
ok = gmc_gui:message({notice, Started}),
|
|
Events =
|
|
[pool_notification,
|
|
connected,
|
|
puzzle,
|
|
result,
|
|
error,
|
|
disconnected],
|
|
lists:foreach(fun gmhc_events:ensure_subscribed/1, Events).
|
|
|
|
optimize_count(MaxMem) ->
|
|
{Procs, Memory} = proc_mem(),
|
|
MeanMaps = min(MaxMem, Memory) div 3550722201,
|
|
Recommended = max(min(Procs, MeanMaps) - 1, 1),
|
|
Notice = fun(F, A) -> gmc_gui:message({notice, io_lib:format(F, A)}) end,
|
|
ok = Notice("Physical Processors: ~p", [Procs]),
|
|
ok = Notice("Physical Memory: ~p", [Memory]),
|
|
ok = Notice("29-bit Mean Map Space: ~p", [MeanMaps]),
|
|
ok = Notice("Workers: ~p", [Recommended]),
|
|
Recommended.
|
|
|
|
proc_mem() ->
|
|
case os:type() of
|
|
{unix, linux} ->
|
|
{processor, Cores} = hd(erlang:system_info(cpu_topology)),
|
|
P = length(Cores),
|
|
M = list_to_integer(string:trim(os:cmd("cat /proc/meminfo | grep MemTotal | awk '{print $2}'"))) * 1024,
|
|
{P, M};
|
|
{unix, darwin} ->
|
|
P = list_to_integer(string:trim(os:cmd("sysctl -n hw.physicalcpu"))),
|
|
M = list_to_integer(string:trim(os:cmd("sysctl -n hw.memsize"))),
|
|
{P, M};
|
|
{win32, nt} ->
|
|
P = list_to_integer(os:getenv("NUMBER_OF_PROCESSORS")),
|
|
M = list_to_integer(string:strip(lists:nth(2, string:split(os:cmd("wmic computersystem get TotalPhysicalMemory"), "\r\r\n", all)))),
|
|
{P, M}
|
|
end.
|
|
|
|
|
|
do_gajudesk() ->
|
|
ok = tell(info, "Running gajudesk"),
|
|
PID = spawn(fun run_gajudesk/0),
|
|
tell(info, "GajuDesk launched at PID: ~p", [PID]).
|
|
|
|
run_gajudesk() ->
|
|
R = "otpr",
|
|
N = "gajudesk",
|
|
{ok, V} = zx:latest({R, N}),
|
|
{ok, PackageString} = zx_lib:package_string({R, N, V}),
|
|
try
|
|
case zx:run(PackageString, []) of
|
|
ok -> ok;
|
|
Error -> tell(error, "gajudesk died with: ~p", [Error])
|
|
end
|
|
catch
|
|
E:R -> tell(error, "gajudesk died with: ~p", [{E, R}])
|
|
end.
|
|
|
|
|
|
run_gmc_conf(State = #s{gmc_conf = none, network = Net, acc = Acc, keys = Keys,
|
|
max_cores = MProcs, max_mem = MMem,
|
|
prefs = Prefs}) ->
|
|
{AProcs, AMem} = proc_mem(),
|
|
Win = gmc_conf:start_link({Prefs, {Net, Acc, Keys, {AProcs, AMem, MProcs, MMem}}}),
|
|
PID = wx_object:get_pid(Win),
|
|
Mon = monitor(process, PID),
|
|
ok = gmc_conf:to_front(),
|
|
State#s{gmc_conf = Mon};
|
|
run_gmc_conf(State) ->
|
|
ok = gmc_conf:to_front(),
|
|
State.
|
|
|
|
|
|
do_conf({Network, AccID, Keys, MaxCores, MaxMem}, State) ->
|
|
State#s{network = Network, acc = AccID, keys = Keys, max_cores = MaxCores, max_mem = MaxMem}.
|
|
|
|
|
|
%%% Utils
|
|
|
|
|
|
%% Paths and Names
|
|
|
|
persist(#s{network = Network, acc = Acc, keys = Keys, max_cores = MaxCores, max_mem = MaxMem, prefs = Prefs}) ->
|
|
Update = #{network => Network, account => Acc, keys => Keys, max_cores => MaxCores, max_mem => MaxMem},
|
|
Conf = maps:to_list(maps:merge(Prefs, Update)),
|
|
Path = gmc_prefs_path(),
|
|
ok = filelib:ensure_dir(Path),
|
|
zx_lib:write_terms(Path, Conf).
|
|
|
|
|
|
read_conf() ->
|
|
Path = gmc_prefs_path(),
|
|
case file:consult(Path) of
|
|
{ok, Conf} ->
|
|
{ok, maps:from_list(Conf)};
|
|
{error, enoent} ->
|
|
none;
|
|
{error, Other} ->
|
|
ok = log(info, "Path ~p could not be read. Failed with ~p. Continuing.", [Path, Other]),
|
|
none
|
|
end.
|
|
|
|
gmc_prefs_path() ->
|
|
filename:join(zx_lib:path(etc, "qpq", "gajumine"), "prefs.eterms").
|