%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2018. 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 for easier measurement of scheduler utilization %% using erlang:statistics(scheduler_wall_time). -module(scheduler). -export([sample/0, sample_all/0, utilization/1, utilization/2]). -export_type([sched_sample/0]). -opaque sched_sample() :: {scheduler_wall_time | scheduler_wall_time_all, [{sched_type(), sched_id(), ActiveTime::integer(), TotalTime::integer()}]}. -type sched_type() :: normal | cpu | io. -type sched_id() :: integer(). -spec sample() -> sched_sample(). sample() -> sample(scheduler_wall_time). -spec sample_all() -> sched_sample(). sample_all() -> sample(scheduler_wall_time_all). sample(Stats) -> case erlang:statistics(Stats) of undefined -> erlang:system_flag(scheduler_wall_time, true), sample(Stats); List -> Sorted = lists:sort(List), Tagged = lists:map(fun({I, A, T}) -> {sched_tag(I), I, A, T} end, Sorted), {Stats, Tagged} end. -type sched_util_result() :: [{sched_type(), sched_id(), float(), string()} | {total, float(), string()} | {weighted, float(), string()}]. -spec utilization(Seconds) -> sched_util_result() when Seconds :: pos_integer(); (Sample) -> sched_util_result() when Sample :: sched_sample(). utilization(Seconds) when is_integer(Seconds), Seconds > 0 -> OldFlag = erlang:system_flag(scheduler_wall_time, true), T0 = sample(), receive after Seconds*1000 -> ok end, T1 = sample(), case OldFlag of false -> erlang:system_flag(scheduler_wall_time, OldFlag); true -> ok end, utilization(T0,T1); utilization({Stats, _}=T0) when Stats =:= scheduler_wall_time; Stats =:= scheduler_wall_time_all -> utilization(T0, sample(Stats)). -spec utilization(Sample1, Sample2) -> sched_util_result() when Sample1 :: sched_sample(), Sample2 :: sched_sample(). utilization({Stats, Ts0}, {Stats, Ts1}) -> Diffs = lists:map(fun({{Tag, I, A0, T0}, {Tag, I, A1, T1}}) -> {Tag, I, (A1 - A0), (T1 - T0)} end, lists:zip(Ts0,Ts1)), {Lst0, {A, T, N}} = lists:foldl(fun({Tag, I, Adiff, Tdiff}, {Lst, Acc}) -> R = safe_div(Adiff, Tdiff), {[{Tag, I, R, percent(R)} | Lst], acc(Tag, Adiff, Tdiff, Acc)} end, {[], {0, 0, 0}}, Diffs), Total = safe_div(A, T), Lst1 = lists:reverse(Lst0), Lst2 = case erlang:system_info(logical_processors_available) of unknown -> Lst1; LPA -> Weighted = Total * (N / LPA), [{weighted, Weighted, percent(Weighted)} | Lst1] end, [{total, Total, percent(Total)} | Lst2]; utilization({scheduler_wall_time, _}=T0, {scheduler_wall_time_all, Ts1}) -> utilization(T0, {scheduler_wall_time, remove_io(Ts1)}); utilization({scheduler_wall_time_all, Ts0}, {scheduler_wall_time, _}=T1) -> utilization({scheduler_wall_time, remove_io(Ts0)}, T1). %% Do not include dirty-io in totals acc(io, _, _, Acc) -> Acc; acc(Tag, Adiff, Tdiff, {Asum, Tsum, N}) when Tag =:= normal; Tag =:= cpu -> {Adiff+Asum, Tdiff+Tsum, N+1}. remove_io(Ts) -> lists:filter(fun({io,_,_,_}) -> false; (_) -> true end, Ts). safe_div(A, B) -> if B == 0.0 -> 0.0; true -> A / B end. sched_tag(Nr) -> Normal = erlang:system_info(schedulers), Cpu = Normal + erlang:system_info(dirty_cpu_schedulers), case Nr of _ when Nr =< Normal -> normal; _ when Nr =< Cpu -> cpu; _ -> io end. percent(F) -> float_to_list(F*100, [{decimals,1}]) ++ [$%].