Current section

Files

Jump to
opencensus src oc_stat_transform.erl
Raw

src/oc_stat_transform.erl

%%%------------------------------------------------------------------------
%% Copyright 2018, OpenCensus Authors
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% 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
%% @end
%%%-----------------------------------------------------------------------
-module(oc_stat_transform).
-export([parse_transform/2]).
%% @doc
%% `oc_stat_transform' is a parse transform that can detect `oc_stat:record' calls
%% with constant measure names and generate remote measure module call from that.
%% At the run-time this means we don't have to do a lookup for the module name and
%% if measure doesn't exist, `{unknown_measure, Name}' error will be thrown.
%% @end
parse_transform(Forms, _Options) ->
HiForms = lists:map(fun walk_ast/1, Forms),
HiForms.
walk_ast({function, Line, Name, Args, Clauses}) ->
{function, Line, Name, Args, walk_clauses([], Clauses)};
walk_ast(Form) ->
Form.
walk_clauses(Acc, []) ->
lists:reverse(Acc);
walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|Rest]) ->
reset_gensym(),
walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], Rest).
walk_body(Acc, []) ->
lists:reverse(Acc);
walk_body(Acc, [H|R]) ->
walk_body([transform_statement(H)|Acc], R).
transform_statement({call, Line,
{remote, _, {atom, _, oc_stat}, {atom, _, record}},
[Tags, {cons, _, _, _} = Measurements]}=_Stmt) ->
gen_record_calls(Line, Tags, erl_syntax:list_elements(Measurements));
transform_statement({call, Line,
{remote, _, {atom, _, oc_stat}, {atom, _, record}},
[Tags, {MType, _, _}=Measurement, Value]}=_Stmt)
when is_atom(MType) orelse is_binary(MType) orelse is_list(MType) ->
gen_record_calls(Line, Tags, [{tuple, Line, [Measurement, Value]}]);
transform_statement(Stmt) when is_tuple(Stmt) ->
list_to_tuple(transform_statement(tuple_to_list(Stmt)));
transform_statement(Stmt) when is_list(Stmt) ->
[transform_statement(S) || S <- Stmt];
transform_statement(Stmt) ->
Stmt.
%% =============================================================================
%% private
%% =============================================================================
gen_record_calls(Line, Tags, Measurements) ->
CTags = {var, Line, gensym("CTags")},
GTags = {var, Line, gensym("GTags")},
{block, Line,
[{match, Line, CTags, Tags},
{match, Line, GTags, gen_prepare_tags(Line, CTags)}]
++
[measure_module_record_call(Line, MeasureName, GTags, Value)
|| {tuple, _, [{_, _, MeasureName}, Value]} <- Measurements]}.
measure_module_record_call(Line, MeasureName, GTags, Value) ->
{'try', Line,
[{call, Line,
{remote, Line, {atom, Line, oc_stat_measure:module_name(MeasureName)}, {atom, Line, record}},
[GTags, Value]}],
[{clause, Line, [{var, Line, '_'}], [], [{atom, 279, 'ok'}]}],
[{clause, Line,
[{tuple, Line,
[{atom, Line, error}, {atom, Line, undef}, {var, Line, '_'}]}],
[],
[{call, Line,
{remote, Line, {atom, Line, erlang}, {atom, Line, error}},
[{tuple, Line,
[{atom, Line, unknown_measure}, erl_parse:abstract(MeasureName)]}]}]}],
[]}.
gen_prepare_tags(Line, CTags) ->
{'case', Line, CTags,
[{clause, Line,
[{var, Line, '_'}],
[[{call, Line, {atom, Line, is_map}, [CTags]}]],
[CTags]},
{clause, Line,
[{var, Line, '_'}],
[],
[{call, Line,
{remote, Line, {atom, Line, oc_tags}, {atom, Line, from_ctx}},
[CTags]}]}]}.
gensym(Name) ->
put(oc_gensym_counter, get(oc_gensym_counter) + 1),
list_to_atom(
lists:flatten(
io_lib:format("$oc_gen_~s_~B$", [Name, get(oc_gensym_counter)]))).
reset_gensym() ->
put(oc_gensym_counter, 0).