%% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% @doc %% Exporter exports the collected records as view data. %% @end -module(oc_stat_exporter). -export([start_link/0, batch_register/1, register/1, register/2, deregister/1, registered/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3, terminate/2]). -export_type([exporter/0]). -compile({no_auto_import, [register/2]}). -type exporter() :: module(). -include("opencensus.hrl"). -include("oc_logger.hrl"). -callback export(ViewData, Config) -> ok when ViewData :: oc_stat_view:view_data(), Config :: any(). -record(state, {exporters :: #{module() => any}, export_interval_ms :: integer(), timer_ref :: reference()}). start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). %% @doc %% Register many `Exporters' at once. %% @end -spec batch_register(Exporters) -> ok when Exporters :: [exporter()]. batch_register(Exporters) when is_list(Exporters) -> gen_server:call(?MODULE, {batch_register, Exporters}, infinity), ok. %% @doc %% @equiv register(Exporter, []) %% @end -spec register(Exporter) -> ok when Exporter :: exporter(). register(Exporter) -> register(Exporter, []), ok. %% @doc %% Registers an `Exporter' with `Config'. %% Collected data will be reported via all the %% registered exporters. Once you no longer %% want data to be exported, invoke {@link deregister/1} %% with the previously registered exporter. %% @end -spec register(Exporter, Config) -> ok when Exporter :: exporter(), Config :: any(). register(Exporter, Config) -> gen_server:call(?MODULE, {register, Exporter, Config}, infinity), ok. %% @doc %% Deregisters an `Exporter'. %% @end -spec deregister(Exporter) -> ok when Exporter :: exporter(). deregister(Exporter) -> gen_server:call(?MODULE, {deregister, Exporter}, infinity), ok. %% @doc %% Checks whether `Exporter' is registered. %% @end -spec registered(Exporter) -> boolean() when Exporter :: exporter(). registered(Exporter) -> gen_server:call(?MODULE, {registered, Exporter}, infinity). %% @doc %% @private %% Called by opencensus %% @end export(Exporters) -> Measurements = oc_stat:export(), [try Exporter:export(Measurements, Config) catch ?WITH_STACKTRACE(Class, Exception, Stacktrace) ?LOG_INFO("stat exporter ~p threw ~p:~p, stacktrace=~p", [Exporter, Class, Exception, Stacktrace]) end || {Exporter, Config} <- Exporters], ok. init(_Args) -> ExportInterval = oc_stat_config:export_interval(), %% TODO: validate Exporters = oc_stat_config:exporters(), %% TODO: should we init exporters here, before exporting? Ref = erlang:send_after(ExportInterval, self(), export_stats), {ok, #state{exporters=to_map(Exporters), export_interval_ms=ExportInterval, timer_ref=Ref}}. handle_call({batch_register, NewExporters}, _From, State=#state{exporters=Exporters}) -> {reply, ok, State#state{exporters=maps:merge(Exporters, maps:from_list(NewExporters))}}; handle_call({register, Exporter, Config}, _From, State=#state{exporters=Exporters}) -> {reply, ok, State#state{exporters=maps:put(Exporter, Config, Exporters)}}; handle_call({deregister, Exporter}, _From, State=#state{exporters=Exporters}) -> {reply, ok, State#state{exporters=maps:remove(Exporter, Exporters)}}; handle_call({registered, Exporter}, _From, State=#state{exporters=Exporters}) -> {reply, maps:is_key(Exporter, Exporters), State}; handle_call(_, _From, State) -> {noreply, State}. handle_cast(_, State) -> {noreply, State}. handle_info(export_stats, State=#state{exporters=Exporters, export_interval_ms=ExportInterval, timer_ref=Ref}) -> erlang:cancel_timer(Ref), Ref1 = erlang:send_after(ExportInterval, self(), export_stats), spawn(fun () -> export(maps:to_list(Exporters)) end), {noreply, State#state{timer_ref=Ref1}}. code_change(_OldVsn, State, _Extra) -> {ok, State}. terminate(_, #state{timer_ref=Ref}) -> erlang:cancel_timer(Ref), ok. to_map(Map) when is_map(Map) -> Map; to_map(List) when is_list(List) -> maps:from_list(List); to_map(Thing) -> erlang:error({invalid_exporters, Thing}).