Wiki

Clone wiki

erlang_exercises / Home

Weather App Overview

The WeatherApp is an Erlang/OTP application that can be used to measure and calculate temperature distributions from a number of weather stations. More specifically, it accepts measurements from different stations and computes hourly temperature histograms accross all stations that have reported temperatures. The latest normalised temperature should be retrieved via a Web Service call.

A high-level perspective of the architecture is described in the diagram below.

high-level-overview.png

The weather_app is the application main entry point. When 'application:ensure_all_started(weather)' is called, 'weather_app:start/2' method would be called next to ensure the weather application, as well as its dependencies (e.g. lager) are started. This method also starts the main supervisor of the application.

'weather_sup' is the main supervisor of the application. During its initialisation, it reads configuration details from the configuration file ("weather_app.config") and starts the worker processes that handle the computations of the temperatures distributions and allows the retrieval of the latest result. The supervisor is also responsible for the restarting of worker processes that fail. In our case, we need a permanent restart type, so that worker processes would always be restarted, no matter what. Indeed, the work to be performed has no ending, as weather stations need to submit temperatures measurements all the time. The restart strategy is one_for_all, which means that when a worker process fails, he and all his siblings have to be restarted. This is important because we keep the result of the calculations in the state itself, so we do not want workers states to become out of sync.

The Web Framework in the diagram is just a placeholder for multiple modules that deal with the web APIs and Web Services.

Now, we dig further into the weather_worker itself. The diagram below describes the taks of the server, its interface and also shows the casts and calls between the server and the helper module that handle new distribution calculation.

weather-worker-overview.png

When the submit method is called by one of the modules in the Web Framework, a cast message is sent to the weather_worker server. It will handle the message in the handle_cast callback:

handle_cast({new_submission, [_StationId, NewTemps]}, #state{temperatures = Temperatures} = State) ->
  {noreply, State#state{temperatures = [NewTemps | Temperatures]}, ?GET_TIMEOUT(State)};

The handling of a new submission just supposes saving the new list of temperatures from the station id identified by StationId in the gen_server state.

Important to note is the GET_TIMEOUT macro:

-define(GET_TIMEOUT(State),
  State#state.computation_timeout - (weather_util:now_ms() - State#state.last_computed_time)).

When a new submission comes to the server, we do not want to affect the hourly calculation of the distribution. That is why, we need to update the current timeout of the server (i.e. how many milliseconds to wait until issue a timeout message to the server). As we keep in the state the last time a distribution was computed and the timeout between computation, getting the new timeout is straightforward and follows the mathematical formula above. In other words, it can be resumed by: if we know that by the moment a new submission was received and the last time the distribution was computed passed X milliseconds, than we have to extract that time from the computation timeout. At this step, the exercise 3 was essential.

When the 'Web Framework' asks for the latest calculated distribution, it call weather_worker:retrieve/0. This sends a call message to the gen_server, handled in the handle_call callback:

handle_call(retrieve_distribution, _From, #state{distribution = Distrib} = State) ->
  lager:log(notice, ?MODULE, "Retrieving the distribution from the state... ~n", []),
  {reply, {ok, Distrib}, State, ?GET_TIMEOUT(State)};

The callback just retrieves the distribution from the server state and returns it to the caller process. The same logic for the timeout update is applied.

The new distribution is called when a timeout message is sent to the server and the handler method is handle_info(timeout, State). It will call further distribution_handler:compute_distribution/1 method, that makes the actual computations.

Technically, compute_distribution method takes the temperatures lists from the weather worker and for each list creates the corresponding histogram (exercise 1) and in the end normalizes the histograms and returns the distribution tuple (exercise 2).

Updated