From 71cabd17def7a322f951715c6cd1d7afb577c5d9 Mon Sep 17 00:00:00 2001 From: gfngfn Date: Sat, 26 Mar 2022 13:06:38 +0900 Subject: [PATCH 1/7] begin to develop 'MetricsServer' --- aws/cloud-formation.template.yaml | 3 + src/Constants.sest | 3 + src/MetricsServer.sest | 99 +++++++++++++++++++++++++++++++ src/RoomResourceServer.sest | 19 ++++++ src/Socket.sest | 26 ++++++++ src/UserResourceServer.sest | 19 ++++++ 6 files changed, 169 insertions(+) create mode 100644 src/MetricsServer.sest create mode 100644 src/Socket.sest diff --git a/aws/cloud-formation.template.yaml b/aws/cloud-formation.template.yaml index 52ef2f4..3a8ec15 100644 --- a/aws/cloud-formation.template.yaml +++ b/aws/cloud-formation.template.yaml @@ -306,6 +306,9 @@ Resources: } ] } + }, + "metrics_collected": { + "emf": {} } } } diff --git a/src/Constants.sest b/src/Constants.sest index a5ff87e..a3c4291 100644 --- a/src/Constants.sest +++ b/src/Constants.sest @@ -21,4 +21,7 @@ module Constants = struct val maximum_num_rooms_per_user() = 10 + val metrics_interval() = + 1000 * 60 /* one minute */ + end diff --git a/src/MetricsServer.sest b/src/MetricsServer.sest new file mode 100644 index 0000000..2b7011c --- /dev/null +++ b/src/MetricsServer.sest @@ -0,0 +1,99 @@ +import Logger +import Constants +import Socket +import RoomResourceServer +import UserResourceServer + +module MetricsServer :> sig + open Stdlib + + type info :: o + + type proc :: o + + val start_link<$a> : fun() -> [$a]result + + val as_pid : fun(proc) -> pid + +end = struct + open Stdlib + + module Callback = struct + + type init_arg = unit + + type request = + | RequestDummy + + type response = + | ResponseDummy + + type cast_message = + | CastDummy + + type info = + | InfoDummy + + type state = { + socket : Socket.t, + } + + type global = unit + + val init({}) = act + do Ok(socket) <- Socket.connect(-address {127, 0, 0, 1}, -port 25888) in + let state = { socket = socket } in + GenServer.init_ok(state, ?timeout Constants.metrics_interval()) + + val handle_call(RequestDummy, from, state) = act + let _ = assert Logger.warning(f'unexpected call (from: ~p, state: ~p)', {from, state}) in + GenServer.reply(ResponseDummy, state) + + val handle_cast(CastDummy, state) = act + let _ = assert Logger.warning(f'unexpected cast (state: ~p)', {state}) in + GenServer.no_reply(state) + + val handle_down(mref, pid, reason, state) = act + let tuple = {mref, pid, reason} in + let _ = assert Logger.warning(f'unexpected down (tuple: ~p, state: ~p)', {tuple, state}) in + GenServer.no_reply(state) + + val now_milliseconds<$a> : fun() -> [$a]int = external 0 ``` + get_timestamp_milliseconds() -> + os:system_time(milli_seconds). + ``` + + val handle_timeout(state) = act + do num_rooms <- RoomResourceServer.get_number_of_rooms() in + do num_users <- UserResourceServer.get_number_of_users() in + do timestamp <- now_milliseconds() in + let data = + format( + f'{"_aws": {"Timestamp": ~p, "CloudWatchMetrics": [{"Namespace": "game-server-metrics", "Dimensions": [["functionVersion"]], "Metrics": [{"Name": "num_rooms": "Unit": "Count"}, {"Name": "num_users", "Unit": "Count"}]}]}, "functionVersion": "$LATEST", "num_rooms": ~p, "num_users": ~p}', {timestamp, num_rooms, num_users}) + in + do Ok({}) <- Socket.send(state.socket, Binary.from_list(data)) in + GenServer.no_reply(state, ?timeout Constants.metrics_interval()) + + val handle_info(info, state : state) = act + let _ = assert Logger.warning(f'unexpected info (info: ~p, state: ~p)', {info, state}) in + GenServer.no_reply(state) + + val terminate(reason, state) = act + let _ = assert Logger.warning(f'terminate (reason: ~p, state: ~p)', {reason, state}) in + return({}) + + end + + module Impl = GenServer.Make(Callback) + + type info = Callback.info + + type proc = Impl.proc + + val start_link() = act + Impl.start_link_name({}, -name Global({})) + + val as_pid(proc) = + Impl.as_pid(proc) + +end diff --git a/src/RoomResourceServer.sest b/src/RoomResourceServer.sest index cdf3ba5..ca34853 100644 --- a/src/RoomResourceServer.sest +++ b/src/RoomResourceServer.sest @@ -14,6 +14,7 @@ module RoomResourceServer :> sig val as_pid : fun(proc) -> pid val add<$a, $b> : fun(-created_by user_id, -room_id room_id, -room_name binary) -> [$a]result + val get_number_of_rooms<$a> : fun() -> [$a]result end = struct open Stdlib open Models @@ -24,9 +25,11 @@ end = struct type request = | AddRoom(user_id, room_id, binary) + | GetNumRooms type response = | RoomAdded(result) + | NumRoomsGot(int) type cast_message = | CastDummy @@ -72,6 +75,9 @@ end = struct | Error(err) -> GenServer.reply(RoomAdded(Error(err)), state) end + + | GetNumRooms -> + GenServer.reply(NumRoomsGot(state.num_rooms), state) end val handle_cast(msg, state) = act @@ -125,4 +131,17 @@ end = struct return(Error(RawValue.forget("no room resource server"))) end + val get_number_of_rooms() = act + do proc_res <- Impl.where_is_global({}) in + case proc_res of + | Some(proc) -> + do call_res <- Impl.call(proc, Callback.GetNumRooms) in + case call_res of + | Ok(Callback.NumRoomsGot(n)) -> return(Ok(n)) + | Error(err) -> return(Error(err)) + end + | None -> + return(Error(RawValue.forget("no room resource server"))) + end + end diff --git a/src/Socket.sest b/src/Socket.sest new file mode 100644 index 0000000..7d95383 --- /dev/null +++ b/src/Socket.sest @@ -0,0 +1,26 @@ +module Socket :> sig + open Stdlib + + type t :: o + + val connect<$a> : fun(-address {int, int, int, int}, -port int) -> [$a]result + + val send<$a> : fun(t, binary) -> [$a]result + +end = struct + open Stdlib + + type t = + | SocketDummy + + val connect<$a> : fun(-address {int, int, int, int}, -port int) -> [$a]result = external 2 ``` + connect(Address, Port) -> + gen_tcp:connect(#{family => inet, port => Port, addr => Address}). + ``` + + val send<$a> : fun(t, binary) -> [$a]result = external 2 ``` + send(Socket, Packet) -> + gen_tcp:send(Socket, Packet). + ``` + +end diff --git a/src/UserResourceServer.sest b/src/UserResourceServer.sest index 512692c..f9fc5e9 100644 --- a/src/UserResourceServer.sest +++ b/src/UserResourceServer.sest @@ -13,6 +13,7 @@ module UserResourceServer :> sig val start_link<$a> : fun() -> [$a]result val as_pid : fun(proc) -> pid val add<$a, $b> : fun(-user_id user_id, -user_name binary) -> [$a]result + val get_number_of_users<$a> : fun() -> [$a]result end = struct open Stdlib open Models @@ -23,9 +24,11 @@ end = struct type request = | AddUser(user_id, binary) + | GetNumUsers type response = | UserAdded(result) + | NumUsersGot(int) type cast_message = | CastDummy @@ -63,6 +66,9 @@ end = struct | Error(err) -> GenServer.reply(UserAdded(Error(err)), state) end + + | GetNumUsers -> + GenServer.reply(NumUsersGot(state.num_users), state) end val handle_cast(msg, state) = act @@ -116,4 +122,17 @@ end = struct return(Error(RawValue.forget("no user resource server"))) end + val get_number_of_users() = act + do proc_res <- Impl.where_is_global({}) in + case proc_res of + | Some(proc) -> + do call_res <- Impl.call(proc, Callback.GetNumUsers) in + case call_res of + | Ok(Callback.NumUsersGot(n)) -> return(Ok(n)) + | Error(err) -> return(Error(err)) + end + | None -> + return(Error(RawValue.forget("no user resource server"))) + end + end From 9f2fa378e2a7caf861e463b36893f76fce2f3311 Mon Sep 17 00:00:00 2001 From: gfngfn Date: Sat, 26 Mar 2022 16:11:43 +0900 Subject: [PATCH 2/7] fixes --- src/MetricsServer.sest | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/MetricsServer.sest b/src/MetricsServer.sest index 2b7011c..d2ab06a 100644 --- a/src/MetricsServer.sest +++ b/src/MetricsServer.sest @@ -59,7 +59,7 @@ end = struct GenServer.no_reply(state) val now_milliseconds<$a> : fun() -> [$a]int = external 0 ``` - get_timestamp_milliseconds() -> + now_milliseconds() -> os:system_time(milli_seconds). ``` @@ -69,7 +69,9 @@ end = struct do timestamp <- now_milliseconds() in let data = format( - f'{"_aws": {"Timestamp": ~p, "CloudWatchMetrics": [{"Namespace": "game-server-metrics", "Dimensions": [["functionVersion"]], "Metrics": [{"Name": "num_rooms": "Unit": "Count"}, {"Name": "num_users", "Unit": "Count"}]}]}, "functionVersion": "$LATEST", "num_rooms": ~p, "num_users": ~p}', {timestamp, num_rooms, num_users}) + f'{\"_aws\": {\"Timestamp\": ~p, \"CloudWatchMetrics\": [{\"Namespace\": \"game-server-metrics\", \"Dimensions\": [[\"functionVersion\"]], \"Metrics\": [{\"Name\": \"num_rooms\": \"Unit\": \"Count\"}, {\"Name\": \"num_users\", \"Unit\": \"Count\"}]}]}, \"functionVersion\": \"$LATEST\", \"num_rooms\": ~p, \"num_users\": ~p}', + {timestamp, num_rooms, num_users}, + ) in do Ok({}) <- Socket.send(state.socket, Binary.from_list(data)) in GenServer.no_reply(state, ?timeout Constants.metrics_interval()) From 21c605412bbe702535d5520ac2d3c963f30e45f4 Mon Sep 17 00:00:00 2001 From: gfngfn Date: Sat, 26 Mar 2022 16:14:58 +0900 Subject: [PATCH 3/7] make 'Sup' start 'MetricsServer' --- src/Sup.sest | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Sup.sest b/src/Sup.sest index 6c320a7..0476a5a 100644 --- a/src/Sup.sest +++ b/src/Sup.sest @@ -3,6 +3,7 @@ import UserServerSup import PlazaServer import RoomResourceServer import UserResourceServer +import MetricsServer module Sup = struct open Stdlib @@ -46,6 +47,12 @@ module Sup = struct return(Result.map(UserResourceServer.as_pid, res)) end) + val start_metrics_server() = act + SS.make_child_proc(fun() -> act + do res <- MetricsServer.start_link() in + return(Result.map(MetricsServer.as_pid, res)) + end) + val init(_) = act let sup_flags = SS.make_sup_flags(?strategy SS.OneForAll) in let child_specs = @@ -75,6 +82,11 @@ module Sup = struct -start (freeze start_user_resource_server()), ?type Supervisor.Worker ), + SS.make_child_spec( + -id "metrics_server", + -start (freeze start_metrics_server()), + ?type Supervisor.Worker + ), ] in SS.init_ok(sup_flags, child_specs) From 04a8354c821e324fa27e2ea345fee7e8be8f7062 Mon Sep 17 00:00:00 2001 From: gfngfn Date: Sat, 26 Mar 2022 16:26:07 +0900 Subject: [PATCH 4/7] fix 'Socket' and 'MetricsServer' --- src/MetricsServer.sest | 24 +++++++++++++++++++----- src/Socket.sest | 2 +- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/MetricsServer.sest b/src/MetricsServer.sest index d2ab06a..7a5927a 100644 --- a/src/MetricsServer.sest +++ b/src/MetricsServer.sest @@ -35,14 +35,23 @@ end = struct | InfoDummy type state = { - socket : Socket.t, + socket : option, } type global = unit val init({}) = act - do Ok(socket) <- Socket.connect(-address {127, 0, 0, 1}, -port 25888) in - let state = { socket = socket } in + do res <- Socket.connect(-address {127, 0, 0, 1}, -port 25888) in + let socket_opt = + case res of + | Ok(socket) -> + Some(socket) + | Error(err) -> + let _ = assert Logger.warning(f'failed to connect (reason: ~p)', {err}) in + None + end + in + let state = { socket = socket_opt } in GenServer.init_ok(state, ?timeout Constants.metrics_interval()) val handle_call(RequestDummy, from, state) = act @@ -63,7 +72,7 @@ end = struct os:system_time(milli_seconds). ``` - val handle_timeout(state) = act + val handle_timeout(state : state) = act do num_rooms <- RoomResourceServer.get_number_of_rooms() in do num_users <- UserResourceServer.get_number_of_users() in do timestamp <- now_milliseconds() in @@ -73,7 +82,12 @@ end = struct {timestamp, num_rooms, num_users}, ) in - do Ok({}) <- Socket.send(state.socket, Binary.from_list(data)) in + do Ok({}) <- + case state.socket of + | Some(socket) -> Socket.send(socket, Binary.from_list(data)) + | None -> return(Ok({})) + end + in GenServer.no_reply(state, ?timeout Constants.metrics_interval()) val handle_info(info, state : state) = act diff --git a/src/Socket.sest b/src/Socket.sest index 7d95383..c257b4c 100644 --- a/src/Socket.sest +++ b/src/Socket.sest @@ -15,7 +15,7 @@ end = struct val connect<$a> : fun(-address {int, int, int, int}, -port int) -> [$a]result = external 2 ``` connect(Address, Port) -> - gen_tcp:connect(#{family => inet, port => Port, addr => Address}). + gen_tcp:connect(Address, Port, []). ``` val send<$a> : fun(t, binary) -> [$a]result = external 2 ``` From c6019720ab93235e6e985fee7cc6fa21c2b693f6 Mon Sep 17 00:00:00 2001 From: gfngfn Date: Sat, 26 Mar 2022 16:49:44 +0900 Subject: [PATCH 5/7] fix 'Socket' about FFIs --- src/MetricsServer.sest | 1 + src/Socket.sest | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/MetricsServer.sest b/src/MetricsServer.sest index 7a5927a..cd54e79 100644 --- a/src/MetricsServer.sest +++ b/src/MetricsServer.sest @@ -75,6 +75,7 @@ end = struct val handle_timeout(state : state) = act do num_rooms <- RoomResourceServer.get_number_of_rooms() in do num_users <- UserResourceServer.get_number_of_users() in + let _ = assert Logger.debug(f'num_rooms: ~p, num_users: ~p', {num_rooms, num_users}) in do timestamp <- now_milliseconds() in let data = format( diff --git a/src/Socket.sest b/src/Socket.sest index c257b4c..f3ae59e 100644 --- a/src/Socket.sest +++ b/src/Socket.sest @@ -20,7 +20,11 @@ end = struct val send<$a> : fun(t, binary) -> [$a]result = external 2 ``` send(Socket, Packet) -> - gen_tcp:send(Socket, Packet). + Result = gen_tcp:send(Socket, Packet), + case Result of + ok -> {ok, ok}; + {error, _} -> Result + end. ``` end From 867d8e61f84bb785db40ad7dabf3ec066531c772 Mon Sep 17 00:00:00 2001 From: gfngfn Date: Sat, 26 Mar 2022 17:35:03 +0900 Subject: [PATCH 6/7] fix how to output metrics --- src/MetricsServer.sest | 43 ++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/MetricsServer.sest b/src/MetricsServer.sest index cd54e79..34df93b 100644 --- a/src/MetricsServer.sest +++ b/src/MetricsServer.sest @@ -73,20 +73,35 @@ end = struct ``` val handle_timeout(state : state) = act - do num_rooms <- RoomResourceServer.get_number_of_rooms() in - do num_users <- UserResourceServer.get_number_of_users() in - let _ = assert Logger.debug(f'num_rooms: ~p, num_users: ~p', {num_rooms, num_users}) in - do timestamp <- now_milliseconds() in - let data = - format( - f'{\"_aws\": {\"Timestamp\": ~p, \"CloudWatchMetrics\": [{\"Namespace\": \"game-server-metrics\", \"Dimensions\": [[\"functionVersion\"]], \"Metrics\": [{\"Name\": \"num_rooms\": \"Unit\": \"Count\"}, {\"Name\": \"num_users\", \"Unit\": \"Count\"}]}]}, \"functionVersion\": \"$LATEST\", \"num_rooms\": ~p, \"num_users\": ~p}', - {timestamp, num_rooms, num_users}, - ) - in - do Ok({}) <- - case state.socket of - | Some(socket) -> Socket.send(socket, Binary.from_list(data)) - | None -> return(Ok({})) + do res_rooms <- RoomResourceServer.get_number_of_rooms() in + do res_users <- UserResourceServer.get_number_of_users() in + do {} <- + case {res_rooms, res_users} of + | {Ok(num_rooms), Ok(num_users)} -> + let _ = assert Logger.debug(f'num_rooms: ~p, num_users: ~p', {num_rooms, num_users}) in + do timestamp <- now_milliseconds() in + let data = + format( + f'{\"_aws\": {\"Timestamp\": ~p, \"CloudWatchMetrics\": [{\"Namespace\": \"game-server-metrics\", \"Dimensions\": [[\"functionVersion\"]], \"Metrics\": [{\"Name\": \"num_rooms\": \"Unit\": \"Count\"}, {\"Name\": \"num_users\", \"Unit\": \"Count\"}]}]}, \"functionVersion\": \"$LATEST\", \"num_rooms\": ~p, \"num_users\": ~p}', + {timestamp, num_rooms, num_users}, + ) + in + case state.socket of + | Some(socket) -> + do res <- Socket.send(socket, Binary.from_list(data)) in + case res of + | Ok({}) -> + return({}) + | Error(err) -> + let _ = assert Logger.warning(f'failed to send metrics (reason: ~p)', {err}) in + return({}) + end + | None -> + return({}) + end + | _ -> + let _ = assert Logger.warning(f'res_rooms: ~p, res_users: ~p', {res_rooms, res_users}) in + return({}) end in GenServer.no_reply(state, ?timeout Constants.metrics_interval()) From bce67f53ecff82446e400cea85ed703ca02fb874 Mon Sep 17 00:00:00 2001 From: gfngfn Date: Sun, 27 Mar 2022 14:16:13 +0900 Subject: [PATCH 7/7] begin to use 'application.log' for Embedded metrics --- config/sys.config | 8 +++++++- src/MetricsLogger.sest | 36 ++++++++++++++++++++++++++++++++++++ src/MetricsServer.sest | 14 ++++++++++---- 3 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 src/MetricsLogger.sest diff --git a/config/sys.config b/config/sys.config index a938ebf..6e33c47 100644 --- a/config/sys.config +++ b/config/sys.config @@ -10,7 +10,13 @@ }, formatter => {logger_formatter, #{ - template => [time, " [", level, "] ", file, ":", line, " ", pid, " ", msg, "\n"] + template => [ + {message_only, + [], + [time, " [", level, "] ", file, ":", line, " ", pid, " "] + }, + msg, "\n" + ] }} }} ]} diff --git a/src/MetricsLogger.sest b/src/MetricsLogger.sest new file mode 100644 index 0000000..46e5402 --- /dev/null +++ b/src/MetricsLogger.sest @@ -0,0 +1,36 @@ +module MetricsLogger :> sig + + type unix_timestamp_milliseconds = int + + val put<$a> : + fun( + -timestamp unix_timestamp_milliseconds, + -num_rooms int, + -num_users int, + ) -> + [$a]unit + +end = struct + open Stdlib + + type unix_timestamp_milliseconds = int + + val put_impl<$a> : fun(list) -> [$a]unit = external 1 ``` + put_impl(Msg) -> + logger:log(info, Msg, [], #{ message_only => "yes", file => "", line => 0 }). + ``` + + val put( + -timestamp timestamp, + -num_rooms num_rooms, + -num_users num_users, + ) = act + let data = + format( + f'{\"_aws\": {\"Timestamp\": ~p, \"CloudWatchMetrics\": [{\"Namespace\": \"game-server-metrics\", \"Dimensions\": [[\"functionVersion\"]], \"Metrics\": [{\"Name\": \"num_rooms\": \"Unit\": \"Count\"}, {\"Name\": \"num_users\", \"Unit\": \"Count\"}]}]}, \"functionVersion\": \"$LATEST\", \"num_rooms\": ~p, \"num_users\": ~p}', + {timestamp, num_rooms, num_users}, + ) + in + put_impl(data) + +end diff --git a/src/MetricsServer.sest b/src/MetricsServer.sest index 34df93b..2310342 100644 --- a/src/MetricsServer.sest +++ b/src/MetricsServer.sest @@ -3,6 +3,7 @@ import Constants import Socket import RoomResourceServer import UserResourceServer +import MetricsLogger module MetricsServer :> sig open Stdlib @@ -52,6 +53,7 @@ end = struct end in let state = { socket = socket_opt } in + let _ = assert Logger.info(f'start (state: ~p)', {state}) in GenServer.init_ok(state, ?timeout Constants.metrics_interval()) val handle_call(RequestDummy, from, state) = act @@ -80,12 +82,14 @@ end = struct | {Ok(num_rooms), Ok(num_users)} -> let _ = assert Logger.debug(f'num_rooms: ~p, num_users: ~p', {num_rooms, num_users}) in do timestamp <- now_milliseconds() in - let data = - format( - f'{\"_aws\": {\"Timestamp\": ~p, \"CloudWatchMetrics\": [{\"Namespace\": \"game-server-metrics\", \"Dimensions\": [[\"functionVersion\"]], \"Metrics\": [{\"Name\": \"num_rooms\": \"Unit\": \"Count\"}, {\"Name\": \"num_users\", \"Unit\": \"Count\"}]}]}, \"functionVersion\": \"$LATEST\", \"num_rooms\": ~p, \"num_users\": ~p}', - {timestamp, num_rooms, num_users}, + do {} <- + MetricsLogger.put( + -timestamp timestamp, + -num_rooms num_rooms, + -num_users num_users, ) in +/* case state.socket of | Some(socket) -> do res <- Socket.send(socket, Binary.from_list(data)) in @@ -99,6 +103,8 @@ end = struct | None -> return({}) end +*/ + return({}) | _ -> let _ = assert Logger.warning(f'res_rooms: ~p, res_users: ~p', {res_rooms, res_users}) in return({})