%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2007-2009. All Rights Reserved. %% %% 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. %% %% %CopyrightEnd% %% @doc Utility functions to operate on percept data. These functions should %% be considered experimental. Behaviour may change in future releases. -module(percept_analyzer). -export([ minmax/1, waiting_activities/1, activities2count/2, activities2count/3, activities2count2/2, analyze_activities/2, runnable_count/1, runnable_count/2, seconds2ts/2, minmax_activities/2, mean/1 ]). -include("percept.hrl"). %%========================================================================== %% %% Interface functions %% %%========================================================================== %% @spec minmax([{X, Y}]) -> {MinX, MinY, MaxX, MaxY} %% X = number() %% Y = number() %% MinX = number() %% MinY = number() %% MaxX = number() %% MaxY = number() %% @doc Returns the min and max of a set of 2-dimensional numbers. minmax(Data) -> Xs = [ X || {X,_Y} <- Data], Ys = [ Y || {_X, Y} <- Data], {lists:min(Xs), lists:min(Ys), lists:max(Xs), lists:max(Ys)}. %% @spec mean([number()]) -> {Mean, StdDev, N} %% Mean = float() %% StdDev = float() %% N = integer() %% @doc Calculates the mean and the standard deviation of a set of %% numbers. mean([]) -> {0, 0, 0}; mean([Value]) -> {Value, 0, 1}; mean(List) -> mean(List, {0, 0, 0}). mean([], {Sum, SumSquare, N}) -> Mean = Sum / N, StdDev = math:sqrt((SumSquare - Sum*Sum/N)/(N - 1)), {Mean, StdDev, N}; mean([Value | List], {Sum, SumSquare, N}) -> mean(List, {Sum + Value, SumSquare + Value*Value, N + 1}). activities2count2(Acts, StartTs) -> Start = inactive_start_states(Acts), activities2count2(Acts, StartTs, Start, []). activities2count2([], _, _, Out) -> lists:reverse(Out); activities2count2([#activity{ id = Id, timestamp = Ts, state = active} | Acts], StartTs, {Proc,Port}, Out) when is_pid(Id) -> activities2count2(Acts, StartTs, {Proc + 1, Port}, [{?seconds(Ts, StartTs), Proc + 1, Port}|Out]); activities2count2([#activity{ id = Id, timestamp = Ts, state = inactive} | Acts], StartTs, {Proc,Port}, Out) when is_pid(Id) -> activities2count2(Acts, StartTs, {Proc - 1, Port}, [{?seconds(Ts, StartTs), Proc - 1, Port}|Out]); activities2count2([#activity{ id = Id, timestamp = Ts, state = active} | Acts], StartTs, {Proc,Port}, Out) when is_port(Id) -> activities2count2(Acts, StartTs, {Proc, Port + 1}, [{?seconds(Ts, StartTs), Proc, Port + 1}|Out]); activities2count2([#activity{ id = Id, timestamp = Ts, state = inactive} | Acts], StartTs, {Proc,Port}, Out) when is_port(Id) -> activities2count2(Acts, StartTs, {Proc, Port - 1}, [{?seconds(Ts, StartTs), Proc, Port - 1}|Out]). inactive_start_states(Acts) -> D = activity_start_states(Acts, dict:new()), dict:fold(fun (K, inactive, {Procs, Ports}) when is_pid(K) -> {Procs + 1, Ports}; (K, inactive, {Procs, Ports}) when is_port(K) -> {Procs, Ports + 1}; (_, _, {Procs, Ports}) -> {Procs, Ports} end, {0,0}, D). activity_start_states([], D) -> D; activity_start_states([#activity{id = Id, state = State}|Acts], D) -> case dict:is_key(Id, D) of true -> activity_start_states(Acts, D); false -> activity_start_states(Acts, dict:store(Id, State, D)) end. %% @spec activities2count(#activity{}, timestamp()) -> Result %% Result = [{Time, ProcessCount, PortCount}] %% Time = float() %% ProcessCount = integer() %% PortCount = integer() %% @doc Calculate the resulting active processes and ports during %% the activity interval. %% Also checks active/inactive consistency. %% A task will always begin with an active state and end with an inactive state. activities2count(Acts, StartTs) when is_list(Acts) -> activities2count(Acts, StartTs, separated). activities2count(Acts, StartTs, Type) when is_list(Acts) -> activities2count_loop(Acts, {StartTs, {0,0}}, Type, []). activities2count_loop([], _, _, Out) -> lists:reverse(Out); activities2count_loop( [#activity{ timestamp = Ts, id = Id, runnable_count = Rc} | Acts], {StartTs, {Procs, Ports}}, separated, Out) -> Time = ?seconds(Ts, StartTs), case Id of Id when is_port(Id) -> Entry = {Time, Procs, Rc}, activities2count_loop(Acts, {StartTs, {Procs, Rc}}, separated, [Entry | Out]); Id when is_pid(Id) -> Entry = {Time, Rc, Ports}, activities2count_loop(Acts, {StartTs, {Rc, Ports}}, separated, [Entry | Out]); _ -> activities2count_loop(Acts, {StartTs,{Procs, Ports}}, separated, Out) end; activities2count_loop( [#activity{ timestamp = Ts, id = Id, runnable_count = Rc} | Acts], {StartTs, {Procs, Ports}}, summated, Out) -> Time = ?seconds(Ts, StartTs), case Id of Id when is_port(Id) -> Entry = {Time, Procs + Rc}, activities2count_loop(Acts, {StartTs, {Procs, Rc}}, summated, [Entry | Out]); Id when is_pid(Id) -> Entry = {Time, Rc + Ports}, activities2count_loop(Acts, {StartTs, {Rc, Ports}}, summated, [Entry | Out]) end. %% @spec waiting_activities([#activity{}]) -> FunctionList %% FunctionList = [{Seconds, Mfa, {Mean, StdDev, N}}] %% Seconds = float() %% Mfa = mfa() %% Mean = float() %% StdDev = float() %% N = integer() %% @doc Calculates the time, both average and total, that a process has spent %% in a receive state at specific function. However, if there are multiple receives %% in a function it cannot differentiate between them. waiting_activities(Activities) -> ListedMfas = waiting_activities_mfa_list(Activities, []), Unsorted = lists:foldl( fun (Mfa, MfaList) -> {Total, WaitingTimes} = get({waiting_mfa, Mfa}), % cleanup erlang:erase({waiting_mfa, Mfa}), % statistics of receive waiting places Stats = mean(WaitingTimes), [{Total, Mfa, Stats} | MfaList] end, [], ListedMfas), lists:sort(fun ({A,_,_},{B,_,_}) -> if A > B -> true; true -> false end end, Unsorted). %% Generate lists of receive waiting times per mfa %% Out: %% ListedMfas = [mfa()] %% Intrisnic: %% get({waiting, mfa()}) -> %% [{waiting, mfa()}, {Total, [WaitingTime]}) %% WaitingTime = float() waiting_activities_mfa_list([], ListedMfas) -> ListedMfas; waiting_activities_mfa_list([Activity|Activities], ListedMfas) -> #activity{id = Pid, state = Act, timestamp = Time, where = MFA} = Activity, case Act of active -> waiting_activities_mfa_list(Activities, ListedMfas); inactive -> % Want to know how long the wait is in a receive, % it is given via the next activity case Activities of [] -> [Info] = percept_db:select(information, Pid), case Info#information.stop of undefined -> % get profile end time Waited = ?seconds( percept_db:select({system,stop_ts}), Time); Time2 -> Waited = ?seconds(Time2, Time) end, case get({waiting_mfa, MFA}) of undefined -> put({waiting_mfa, MFA}, {Waited, [Waited]}), [MFA | ListedMfas]; {Total, TimedMfa} -> put({waiting_mfa, MFA}, {Total + Waited, [Waited | TimedMfa]}), ListedMfas end; [#activity{timestamp=Time2, id = Pid, state = active} | _ ] -> % Calculate waiting time Waited = ?seconds(Time2, Time), % Get previous entry case get({waiting_mfa, MFA}) of undefined -> % add entry to list put({waiting_mfa, MFA}, {Waited, [Waited]}), waiting_activities_mfa_list(Activities, [MFA|ListedMfas]); {Total, TimedMfa} -> put({waiting_mfa, MFA}, {Total + Waited, [Waited | TimedMfa]}), waiting_activities_mfa_list(Activities, ListedMfas) end; _ -> error end end. %% seconds2ts(Seconds, StartTs) -> TS %% In: %% Seconds = float() %% StartTs = timestamp() %% Out: %% TS = timestamp() %% @spec seconds2ts(float(), StartTs::{integer(),integer(),integer()}) -> timestamp() %% @doc Calculates a timestamp given a duration in seconds and a starting timestamp. seconds2ts(Seconds, {Ms, S, Us}) -> % Calculate mega seconds integer MsInteger = trunc(Seconds) div 1000000 , % Calculate the reminder for seconds SInteger = trunc(Seconds), % Calculate the reminder for micro seconds UsInteger = trunc((Seconds - SInteger) * 1000000), % Wrap overflows UsOut = (UsInteger + Us) rem 1000000, SOut = ((SInteger + S) + (UsInteger + Us) div 1000000) rem 1000000, MsOut = (MsInteger+ Ms) + ((SInteger + S) + (UsInteger + Us) div 1000000) div 1000000, {MsOut, SOut, UsOut}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Analyze interval for concurrency % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% @spec analyze_activities(integer(), [#activity{}]) -> [{integer(),#activity{}}] %% @hidden analyze_activities(Threshold, Activities) -> RunnableCount = runnable_count(Activities, 0), analyze_runnable_activities(Threshold, RunnableCount). %% runnable_count(Activities, StartValue) -> RunnableCount %% In: %% Activities = [activity()] %% StartValue = integer() %% Out: %% RunnableCount = [{integer(), activity()}] %% Purpose: %% Calculate the runnable count of a given interval of generic %% activities. %% @spec runnable_count([#activity{}]) -> [{integer(),#activity{}}] %% @hidden runnable_count(Activities) -> Threshold = runnable_count_threshold(Activities), runnable_count(Activities, Threshold, []). runnable_count_threshold(Activities) -> CountedActs = runnable_count(Activities, 0), Counts = [C || {C, _} <- CountedActs], Min = lists:min(Counts), 0 - Min. %% @spec runnable_count([#activity{}],integer()) -> [{integer(),#activity{}}] %% @hidden runnable_count(Activities, StartCount) when is_integer(StartCount) -> runnable_count(Activities, StartCount, []). runnable_count([], _ , Out) -> lists:reverse(Out); runnable_count([A | As], PrevCount, Out) -> case A#activity.state of active -> runnable_count(As, PrevCount + 1, [{PrevCount + 1, A} | Out]); inactive -> runnable_count(As, PrevCount - 1, [{PrevCount - 1, A} | Out]) end. %% In: %% Threshold = integer(), %% RunnableActivities = [{Rc, activity()}] %% Rc = integer() analyze_runnable_activities(Threshold, RunnableActivities) -> analyze_runnable_activities(Threshold, RunnableActivities, []). analyze_runnable_activities( _z, [], Out) -> lists:reverse(Out); analyze_runnable_activities(Threshold, [{Rc, Act} | RunnableActs], Out) -> if Rc =< Threshold -> analyze_runnable_activities(Threshold, RunnableActs, [{Rc,Act} | Out]); true -> analyze_runnable_activities(Threshold, RunnableActs, Out) end. %% minmax_activity(Activities, Count) -> {Min, Max} %% In: %% Activities = [activity()] %% InitialCount = non_neg_integer() %% Out: %% {Min, Max} %% Min = non_neg_integer() %% Max = non_neg_integer() %% Purpose: %% Minimal and maximal activity during an activity interval. %% Initial activity count needs to be supplied. %% @spec minmax_activities([#activity{}], integer()) -> {integer(), integer()} %% @doc Calculates the minimum and maximum of runnable activites (processes % and ports) during the interval of reffered by the activity list. minmax_activities(Activities, Count) -> minmax_activities(Activities, Count, {Count, Count}). minmax_activities([], _, Out) -> Out; minmax_activities([A|Acts], Count, {Min, Max}) -> case A#activity.state of active -> minmax_activities(Acts, Count + 1, {Min, lists:max([Count + 1, Max])}); inactive -> minmax_activities(Acts, Count - 1, {lists:min([Count - 1, Min]), Max}) end.