pax_global_header00006660000000000000000000000064122432423100014503gustar00rootroot0000000000000052 comment=e10c05aa91d2fb012bd3323c9254f345cdb76fe8 erlang-folsom-0.8.0+dfsg/000077500000000000000000000000001224324231000151745ustar00rootroot00000000000000erlang-folsom-0.8.0+dfsg/.gitignore000066400000000000000000000000331224324231000171600ustar00rootroot00000000000000/deps /.eunit ebin/* *# *~ erlang-folsom-0.8.0+dfsg/.mailmap000066400000000000000000000001671224324231000166210ustar00rootroot00000000000000Joe Williams Joe Williams erlang-folsom-0.8.0+dfsg/LICENSE000066400000000000000000000251421224324231000162050ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. erlang-folsom-0.8.0+dfsg/README.md000066400000000000000000000171131224324231000164560ustar00rootroot00000000000000### folsom Folsom is an Erlang based metrics system inspired by Coda Hale's metrics (https://github.com/codahale/metrics/). The metrics API's purpose is to collect realtime metrics from your Erlang applications and publish them via Erlang APIs and output plugins. folsom is *not* a persistent store. There are 6 types of metrics: counters, gauges, histograms (and timers), histories, meter_readers and meters. Metrics can be created, read and updated via the `folsom_metrics` module. #### Building and running First, regarding using folsom and folsom_webmachine together. To make sure you have compatible versions of each, make sure you use code from the same version tags, ie 0.5 of folsom is known to work with 0.5 folsom_webmachine. HEAD on each repo may have broken API compatibility. You need a (preferably recent) version of Erlang installed but that should be it. ./rebar get-deps compile folsom can be run standalone or embedded in an Erlang application. $ erl -pa ebin deps/*/ebin > folsom:start(). % this creates the needed ETS tables and starts a gen_server You can also start it as an application: $ erl -pa ebin deps/*/ebin > application:start(folsom). $ erl -pa ebin deps/*/ebin -s folsom The application can be configured to create individual or lists of metrics at startup on the command line or in an application config file: $ erl -pa ebin deps/*/ebin -s folsom \ -folsom history '[hist1,hist2]' \ -folsom gauge gauge1 $ echo '[{folsom, [{history, [hist1, hist2]}, {gauge, gauge1}]}].' \ > myapp.config $ erl -pa ebin deps/*/ebin -config myapp.config -s folsom #### Metrics API folsom_metrics.erl is the API module you will need to use most of the time. Retrieve a list of current installed metrics: > folsom_metrics:get_metrics(). Query a specific metric: > folsom_metrics:get_metric_value(Name). Generally names of metrics are atoms or binaries. ##### Counters Counter metrics provide increment and decrement capabilities for a single scalar value. > folsom_metrics:new_counter(Name). > folsom_metrics:notify({Name, {inc, Value}}). > folsom_metrics:notify({Name, {dec, Value}}). ##### Gauges Gauges are point-in-time single value metrics. > folsom_metrics:new_gauge(Name). > folsom_metrics:notify({Name, Value}). ##### Histograms (and Timers) Histograms are collections of values that have statistical analysis done to them, such as mean, min, max, kurtosis and percentile. They can be used like "timers" as well with the timed update functions. > folsom_metrics:new_histogram(Name). > folsom_metrics:histogram_timed_update(Name, Mod, Fun, Args). > folsom_metrics:histogram_timed_update(Name, Fun, Args). > folsom_metrics:histogram_timed_update(Name, Fun). > folsom_metrics:notify({Name, Value}). ###### Histogram sample types Each histogram draws its values from a `reservoir` of readings. You can select a `sample type` for a histogram by passing the name of the sample type as an atom when you create a new histogram. Some sample types have further arguments. The purpose of a sample type is to control the size and charecteristics of the reservoir of readings the histogram performs analysis upon. Folsom currently provides the following sample types: ###### `uniform` This is a random uniform sample over the stream of readings. This is the default sample type, bounded in size to 1028 readings. When `size` readings have been taken, new readings replace older readings in the reservoir at random. You can set the sample size at creation time: > folsom_metrics:new_histogram(Name, uniform, Size::integer()). Be sure you understand _why_ before you do this. ###### `exdec` This is a sample that exponentially decays less significant readings over time so as to give greater significance to newer readings. Read more here - [Forward Decay...](http://www.research.att.com/people/Cormode_Graham/library/publications/CormodeShkapenyukSrivastavaXu09.pdf). Again you can change defaults at creation time, if you think you need to: > folsom_metrics:new_histogram(Name, exdec, Size::integer(), Alpha::float()). ###### `slide` This is a sliding window in time over a stream of readings. The default window size is 60 seconds. Every reading that occurs in a sliding sixty second window is stored, with older readings being discarded. If you have a lot of readings per minute the `reservoir` may get pretty big and so it will take more time to calculate statistics. You can set the `window` size by providing a number of seconds. > folsom_metrics:new_histogram(Name, slide, Seconds::integer()). ###### `slide_uniform` This is a sliding window in time over a stream of readings with a random uniform sample per second, to bound the size of the total number of readings. The maximum size of the reservoir will be `window size * sample size`. Default is a window of 60 seconds and a sample size of 1028. Again, you can change these at creation time: > folsom_metrics:new_histogram(Name, slide_uniform, {Secs::interger(), Size::integer()). ##### Histories Histories are a collection of past events, such as errors or log messages. > folsom_metrics:new_history(Name). > folsom_metrics:get_history_values(Name, Count). % get more than the default number of history items back > folsom_metrics:notify({Name, Value}). ##### Meters Meters are increment only counters with mean rates and exponentially weighted moving averages applied to them, similar to a unix load average. > folsom_metrics:new_meter(Name). > folsom_metrics:notify({Name, Value}). ###### `Spiral` meter A `spiral` is a type of meter that has a one minute sliding window count. The meter tracks an increment only counter and a total for the last minute. This is a sliding count with older readings dropping off per second. > folsom_metrics:new_spiral(Name). > folsom_metrics:notify({Name, Count}). ##### Meter Reader Meter readers are like a meter except that the values passed to it are monotonically increasing, e.g., reading from a water or gas meter, CPU jiffies, or I/O operation count. > folsom_metrics:new_meter_reader(Name). > folsom_metrics:notify({Name, Value}). ##### Metrics groups/tags Certain users might want to group and query metrics monitoring a common task. In order to do so, they can tag metrics: > folsom_metrics:tag_metric(Name, Tag). and untag metrics: > folsom_metrics:untag_metric(Name, Tag). Users can query a list of tuples `[{Name, Value}]` of all metrics with a given tag: > folsom_metrics:get_metrics_value(Tag). If only a certain type of metrics from a given group is desired, one can specify so: > folsom_metrics:get_metrics_value(Tag, Type). where Type is one of `counter`, `gauge`, `histogram`, `history`, `meter`, `meter_reader`, `duration` or `spiral`. ##### Erlang VM folsom also produces Erlang VM statistics. The result of `erlang:memory/0`: > folsom_vm_metrics:get_memory(). The result of `erlang:system_info/1`: > folsom_vm_metrics:get_system_info(). The result of `erlang:statistics/1`: > folsom_vm_metrics:get_statistics(). The result of `erlang:process_info/1`: > folsom_vm_metrics:get_process_info(). %% use with caution The result of `inet:getstat/1`, `prim_inet:getstatus/1`, `erlang:port_info/1`, `prim_inet:gettype/1`, `inet:getopts/1`, `inet:sockname/1`: > folsom_vm_metrics:get_port_info(). %% use with caution The result from `ets:info/1` and `dets:info/1` across all tables > folsom_vm_metrics:get_ets_info(). > folsom_vm_metrics:get_dets_info(). erlang-folsom-0.8.0+dfsg/include/000077500000000000000000000000001224324231000166175ustar00rootroot00000000000000erlang-folsom-0.8.0+dfsg/include/folsom.hrl000066400000000000000000000150701224324231000206300ustar00rootroot00000000000000-define(FOLSOM_TABLE, folsom). -define(COUNTER_TABLE, folsom_counters). -define(GAUGE_TABLE, folsom_gauges). -define(HISTOGRAM_TABLE, folsom_histograms). -define(METER_TABLE, folsom_meters). -define(METER_READER_TABLE, folsom_meter_readers). -define(HISTORY_TABLE, folsom_histories). -define(DURATION_TABLE, folsom_durations). -define(SPIRAL_TABLE, folsom_spirals). -define(DEFAULT_LIMIT, 5). -define(DEFAULT_SIZE, 1028). % mimic codahale's metrics -define(DEFAULT_SLIDING_WINDOW, 60). % sixty second sliding window -define(DEFAULT_ALPHA, 0.015). % mimic codahale's metrics -define(DEFAULT_INTERVAL, 5000). -define(DEFAULT_SAMPLE_TYPE, uniform). -record(spiral, { tid = folsom_metrics_histogram_ets:new(folsom_spiral, [set, {write_concurrency, true}, public]), server }). -record(slide, { window = ?DEFAULT_SLIDING_WINDOW, reservoir = folsom_metrics_histogram_ets:new(folsom_slide, [duplicate_bag, {write_concurrency, true}, public]), server }). -record(slide_uniform, { window = ?DEFAULT_SLIDING_WINDOW, size = ?DEFAULT_SIZE, reservoir = folsom_metrics_histogram_ets:new(folsom_slide_uniform,[set, {write_concurrency, true}, public]), seed = os:timestamp(), server }). -record(uniform, { size = ?DEFAULT_SIZE, n = 1, reservoir = folsom_metrics_histogram_ets:new(folsom_uniform,[set, {write_concurrency, true}, public]), seed = os:timestamp() }). -record(exdec, { start = 0, next = 0, alpha = ?DEFAULT_ALPHA, size = ?DEFAULT_SIZE, seed = os:timestamp(), n = 1, reservoir = folsom_metrics_histogram_ets:new(folsom_exdec,[ordered_set, {write_concurrency, true}, public]) }). -record(none, { size = ?DEFAULT_SIZE, n = 1, reservoir = folsom_metrics_histogram_ets:new(folsom_none,[ordered_set, {write_concurrency, true}, public]) }). -record(slide_sorted, { size = ?DEFAULT_SIZE, n = 0, reservoir = folsom_metrics_histogram_ets:new(folsom_slide_sorted,[ordered_set, {write_concurrency, true}, public]) }). -record(histogram, { type = uniform, sample = #uniform{} }). -record(history, { tid }). -define(SYSTEM_INFO, [ allocated_areas, allocator, alloc_util_allocators, build_type, c_compiler_used, check_io, compat_rel, cpu_topology, creation, debug_compiled, dist, dist_ctrl, driver_version, elib_malloc, dist_buf_busy_limit, %fullsweep_after, % included in garbage_collection garbage_collection, %global_heaps_size, % deprecated heap_sizes, heap_type, info, kernel_poll, loaded, logical_processors, logical_processors_available, logical_processors_online, machine, %min_heap_size, % included in garbage_collection %min_bin_vheap_size, % included in garbage_collection modified_timing_level, multi_scheduling, multi_scheduling_blockers, otp_release, process_count, process_limit, scheduler_bind_type, scheduler_bindings, scheduler_id, schedulers, schedulers_online, smp_support, system_version, system_architecture, threads, thread_pool_size, trace_control_word, update_cpu_info, version, wordsize ]). -define(STATISTICS, [ context_switches, %exact_reductions, % use reductions instead garbage_collection, io, reductions, run_queue, runtime, wall_clock ]). -define(PROCESS_INFO, [ backtrace, binary, catchlevel, current_function, %dictionary, error_handler, garbage_collection, group_leader, heap_size, initial_call, links, last_calls, memory, %message_binary, message_queue_len, messages, min_heap_size, min_bin_vheap_size, monitored_by, monitors, priority, reductions, registered_name, sequential_trace_token, stack_size, status, suspending, total_heap_size, trace, trap_exit ]). -define(SOCKET_OPTS, [ active, broadcast, delay_send, dontroute, exit_on_close, header, keepalive, nodelay, packet, packet_size, read_packets, recbuf, reuseaddr, send_timeout, send_timeout_close, sndbuf, priority, tos ]). erlang-folsom-0.8.0+dfsg/rebar.config000066400000000000000000000003601224324231000174550ustar00rootroot00000000000000{sub_dirs, ["deps"]}. {deps, [ {'bear', ".*", {git, "git://github.com/boundary/bear.git", {tag, "0.8.0"}}}, {meck, ".*", {git, "git://github.com/eproxus/meck", {tag, "0.7.2"}}} ]}. {erl_opts, [debug_info]}. {cover_enabled, true}. erlang-folsom-0.8.0+dfsg/src/000077500000000000000000000000001224324231000157635ustar00rootroot00000000000000erlang-folsom-0.8.0+dfsg/src/folsom.app.src000066400000000000000000000005161224324231000205540ustar00rootroot00000000000000%% -*- mode: erlang -*- {application, folsom, [ {description, ""}, {vsn, git}, {registered, [folsom_meter_timer_server, folsom_metrics_histogram_ets, folsom_sup]}, {applications, [ kernel, stdlib ]}, {env, []}, {mod, {folsom, []}} ]}. erlang-folsom-0.8.0+dfsg/src/folsom.erl000066400000000000000000000034111224324231000177650ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom.erl %%% @author joe williams %%% @doc %%% @end %%%------------------------------------------------------------------ -module(folsom). -export([start/0, stop/0]). -export([start/2, stop/1]). -behaviour(application). -define(APP, ?MODULE). start() -> application:start(?APP). stop() -> application:stop(?APP). start(_Type, _Args) -> {ok, Pid} = folsom_sup:start_link(), lists:foreach(fun configure/1, [{counter, new_counter}, {gauge, new_gauge}, {histogram, new_histogram}, {history, new_history}, {meter, new_meter}, {meter_reader, new_meter_reader}]), {ok, Pid}. stop(_State) -> ok. %% internal configure({K, New}) -> case application:get_env(?APP, K) of {ok, Specs} when is_list(Specs) -> [configure_metric(New, Spec) || Spec <- Specs]; {ok, Spec} -> configure_metric(New, Spec); undefined -> ok end. configure_metric(New, Spec) when is_list(Spec) -> apply(folsom_metrics, New, Spec); configure_metric(New, Name) -> folsom_metrics:New(Name). erlang-folsom-0.8.0+dfsg/src/folsom_ets.erl000066400000000000000000000352511224324231000206470ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_ets.erl %%% @author joe williams %%% @doc %%% @end %%%------------------------------------------------------------------ -module(folsom_ets). %% API -export([ add_handler/2, add_handler/3, add_handler/4, add_handler/5, tag_handler/2, untag_handler/2, delete_handler/1, handler_exists/1, notify/1, notify/2, notify/3, tagged_notify/4, notify_existing_metric/3, get_handlers/0, get_handlers_info/0, get_info/1, get_values/1, get_history_values/2, get_group_values/1, get_group_values/2, get_tags/1 ]). -record(metric, { tags = sets:new(), type, history_size }). -include("folsom.hrl"). %%%=================================================================== %%% API %%%=================================================================== add_handler(Type, Name) -> maybe_add_handler(Type, Name, handler_exists(Name)). add_handler(Type, Name, SampleSize) -> maybe_add_handler(Type, Name, SampleSize, handler_exists(Name)). add_handler(Type, Name, SampleType, SampleSize) -> maybe_add_handler(Type, Name, SampleType, SampleSize, handler_exists(Name)). add_handler(Type, Name, SampleType, SampleSize, Alpha) -> maybe_add_handler(Type, Name, SampleType, SampleSize, Alpha, handler_exists(Name)). tag_handler(Name, Tag) -> case handler_exists(Name) of true -> add_tag(Name, Tag); false -> {error, Name, nonexistent_metric} end. untag_handler(Name, Tag) -> case handler_exists(Name) of true -> rm_tag(Name, Tag); false -> {error, Name, nonexistent_metric} end. delete_handler(Name) -> {_, Info} = get_info(Name), ok = delete_metric(Name, proplists:get_value(type, Info)). handler_exists(Name) -> ets:member(?FOLSOM_TABLE, Name). %% old tuple style notifications notify({Name, Event}) -> notify(Name, Event). %% notify/2, checks metric type and makes sure metric exists %% before notifying, returning error if not notify(Name, Event) -> case handler_exists(Name) of true -> {_, Info} = get_info(Name), Type = proplists:get_value(type, Info), notify(Name, Event, Type, true); false -> {error, Name, nonexistent_metric} end. %% notify/3, makes sure metric exist, if not creates metric notify(Name, Event, Type) -> notify(Name, Event, Type, handler_exists(Name)). tagged_notify(Name, Event, Type, Tags) -> R = notify(Name, Event, Type), case get_tags(Name) of {error, _, _} -> skip; CurrentTags -> [add_tag(Name, T) || T <- Tags, not sets:is_element(T, CurrentTags)] end, R. %% assumes metric already exists, bypasses above checks notify_existing_metric(Name, Event, Type) -> notify(Name, Event, Type, true). get_handlers() -> proplists:get_keys(ets:tab2list(?FOLSOM_TABLE)). get_handlers_info() -> [get_info(Id) || Id <- get_handlers()]. get_info(Name) -> case handler_exists(Name) of true -> [{_, #metric{type = Type, tags = Tags}}] = ets:lookup(?FOLSOM_TABLE, Name), {Name, [{type, Type}, {tags, Tags}]}; false -> {error, Name, nonexistent_metric} end. get_values(Name) -> case handler_exists(Name) of true -> {_, Info} = get_info(Name), get_values(Name, proplists:get_value(type, Info)); false -> {error, Name, nonexistent_metric} end. get_values(Name, counter) -> folsom_metrics_counter:get_value(Name); get_values(Name, gauge) -> folsom_metrics_gauge:get_value(Name); get_values(Name, histogram) -> folsom_metrics_histogram:get_values(Name); get_values(Name, history) -> folsom_metrics_history:get_events(Name); get_values(Name, meter) -> folsom_metrics_meter:get_values(Name); get_values(Name, meter_reader) -> folsom_metrics_meter_reader:get_values(Name); get_values(Name, duration) -> folsom_metrics_duration:get_values(Name); get_values(Name, spiral) -> folsom_metrics_spiral:get_values(Name); get_values(_, Type) -> {error, Type, unsupported_metric_type}. get_history_values(Name, Count) -> folsom_metrics_history:get_events(Name, Count). get_group_values(Tag) -> folsom_ets:get_group_values(Tag, '_'). get_group_values(Tag, Type) -> Metrics = ets:match(?FOLSOM_TABLE, {'$1', {metric, '$2', Type, '_'}}), [{Name, get_values(Name)} || [Name, Tags] <- Metrics, sets:is_element(Tag, Tags)]. get_tags(Name) -> case handler_exists(Name) of true -> {_, Info} = get_info(Name), proplists:get_value(tags, Info); false -> {error, Name, nonexistent_metric} end. %%%=================================================================== %%% Internal functions %%%=================================================================== maybe_add_handler(counter, Name, false) -> true = folsom_metrics_counter:new(Name), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = counter}}), ok; maybe_add_handler(gauge, Name, false) -> true = folsom_metrics_gauge:new(Name), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = gauge}}), ok; maybe_add_handler(histogram, Name, false) -> true = folsom_metrics_histogram:new(Name), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = histogram}}), ok; maybe_add_handler(duration, Name, false) -> true = folsom_metrics_histogram:new(Name), true = folsom_metrics_duration:new(Name), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = duration}}), ok; maybe_add_handler(history, Name, false) -> ok = folsom_metrics_history:new(Name), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = history, history_size = ?DEFAULT_SIZE}}), ok; maybe_add_handler(meter, Name, false) -> ok = folsom_meter_timer_server:register(Name, folsom_metrics_meter), true = folsom_metrics_meter:new(Name), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = meter}}), ok; maybe_add_handler(meter_reader, Name, false) -> ok = folsom_meter_timer_server:register(Name, folsom_metrics_meter_reader), true = folsom_metrics_meter_reader:new(Name), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = meter_reader}}), ok; maybe_add_handler(spiral, Name, false) -> true = folsom_metrics_spiral:new(Name), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = spiral}}), ok; maybe_add_handler(Type, _, false) -> {error, Type, unsupported_metric_type}; maybe_add_handler(_, Name, true) -> {error, Name, metric_already_exists}. maybe_add_handler(histogram, Name, SampleType, false) -> true = folsom_metrics_histogram:new(Name, SampleType), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = histogram}}), ok; maybe_add_handler(history, Name, SampleSize, false) -> ok = folsom_metrics_history:new(Name), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = history, history_size = SampleSize}}), ok; maybe_add_handler(Type, _, _, false) -> {error, Type, unsupported_metric_type}; maybe_add_handler(_, Name, _, true) -> {error, Name, metric_already_exists}. maybe_add_handler(histogram, Name, SampleType, SampleSize, false) -> true = folsom_metrics_histogram:new(Name, SampleType, SampleSize), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = histogram}}), ok; maybe_add_handler(Type, _, _, _, false) -> {error, Type, unsupported_metric_type}; maybe_add_handler(_, Name, _, _, true) -> {error, Name, metric_already_exists}. maybe_add_handler(histogram, Name, SampleType, SampleSize, Alpha, false) -> true = folsom_metrics_histogram:new(Name, SampleType, SampleSize, Alpha), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = histogram}}), ok; maybe_add_handler(duration, Name, SampleType, SampleSize, Alpha, false) -> true = folsom_metrics_histogram:new(Name, SampleType, SampleSize, Alpha), true = folsom_metrics_duration:new(Name), true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = duration}}), ok; maybe_add_handler(Type, _, _, _, _, false) -> {error, Type, unsupported_metric_type}; maybe_add_handler(_, Name, _, _, _, true) -> {error, Name, metric_already_exists}. add_tag(Name, Tag) -> M = #metric{tags=Tags} = ets:lookup_element(?FOLSOM_TABLE, Name, 2), true = ets:update_element(?FOLSOM_TABLE, Name, {2, M#metric{tags=sets:add_element(Tag, Tags)}}), ok. rm_tag(Name, Tag) -> M = #metric{tags=Tags} = ets:lookup_element(?FOLSOM_TABLE, Name, 2), true = ets:update_element(?FOLSOM_TABLE, Name, {2, M#metric{tags=sets:del_element(Tag, Tags)}}), ok. delete_metric(Name, history) -> History = folsom_metrics_history:get_value(Name), ok = delete_history(Name, History), ok; delete_metric(Name, histogram) -> Metric = folsom_metrics_histogram:get_value(Name), ok = delete_histogram(Name, Metric), ok; delete_metric(Name, duration) -> Histo = folsom_metrics_histogram:get_value(Name), ok = delete_histogram(Name, Histo), true = ets:delete(?DURATION_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), ok; delete_metric(Name, counter) -> ok = folsom_metrics_counter:delete(Name), true = ets:delete(?FOLSOM_TABLE, Name), ok; delete_metric(Name, gauge) -> true = ets:delete(?GAUGE_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), ok; delete_metric(Name, meter) -> ok = folsom_meter_timer_server:unregister(Name), true = ets:delete(?METER_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), ok; delete_metric(Name, meter_reader) -> ok = folsom_meter_timer_server:unregister(Name), true = ets:delete(?METER_READER_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), ok; delete_metric(Name, spiral) -> #spiral{tid=Tid, server=Pid} = folsom_metrics_spiral:get_value(Name), folsom_sample_slide_server:stop(Pid), ets:delete(?SPIRAL_TABLE, Name), ets:delete(?FOLSOM_TABLE, Name), ets:delete(Tid), ok. delete_histogram(Name, #histogram{type = uniform, sample = #uniform{reservoir = Reservoir}}) -> true = ets:delete(?HISTOGRAM_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), true = ets:delete(Reservoir), ok; delete_histogram(Name, #histogram{type = none, sample = #none{reservoir = Reservoir}}) -> true = ets:delete(?HISTOGRAM_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), true = ets:delete(Reservoir), ok; delete_histogram(Name, #histogram{type = slide_sorted, sample = #slide_sorted{reservoir = Reservoir}}) -> true = ets:delete(?HISTOGRAM_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), true = ets:delete(Reservoir), ok; delete_histogram(Name, #histogram{type = exdec}) -> true = ets:delete(?HISTOGRAM_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), ok; delete_histogram(Name, #histogram{type = slide, sample = #slide{reservoir = Reservoir, server=Pid}}) -> folsom_sample_slide_server:stop(Pid), true = ets:delete(?HISTOGRAM_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), true = ets:delete(Reservoir), ok; delete_histogram(Name, #histogram{type = slide_uniform, sample = #slide_uniform{reservoir = Reservoir, server=Pid}}) -> folsom_sample_slide_server:stop(Pid), true = ets:delete(?HISTOGRAM_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), true = ets:delete(Reservoir), ok. delete_history(Name, #history{tid = Tid}) -> true = ets:delete(?HISTORY_TABLE, Name), true = ets:delete(?FOLSOM_TABLE, Name), true = ets:delete(Tid), ok. notify(Name, {inc, Value}, counter, true) -> folsom_metrics_counter:inc(Name, Value), ok; notify(Name, {inc, Value}, counter, false) -> add_handler(counter, Name), folsom_metrics_counter:inc(Name, Value), ok; notify(Name, {dec, Value}, counter, true) -> folsom_metrics_counter:dec(Name, Value), ok; notify(Name, {dec, Value}, counter, false) -> add_handler(counter, Name), folsom_metrics_counter:dec(Name, Value), ok; notify(Name, clear, counter, true) -> folsom_metrics_counter:clear(Name), ok; notify(Name, clear, counter, false) -> add_handler(counter, Name), folsom_metrics_counter:clear(Name), ok; notify(Name, Value, gauge, true) -> folsom_metrics_gauge:update(Name, Value), ok; notify(Name, Value, gauge, false) -> add_handler(gauge, Name), folsom_metrics_gauge:update(Name, Value), ok; notify(Name, Value, histogram, true) -> folsom_metrics_histogram:update(Name, Value), ok; notify(Name, Value, histogram, false) -> add_handler(histogram, Name), folsom_metrics_histogram:update(Name, Value), ok; notify(Name, Value, history, true) -> [{_, #metric{history_size = HistorySize}}] = ets:lookup(?FOLSOM_TABLE, Name), folsom_metrics_history:update(Name, HistorySize, Value), ok; notify(Name, Value, history, false) -> add_handler(history, Name), [{_, #metric{history_size = HistorySize}}] = ets:lookup(?FOLSOM_TABLE, Name), folsom_metrics_history:update(Name, HistorySize, Value), ok; notify(Name, Value, meter, true) -> folsom_metrics_meter:mark(Name, Value), ok; notify(Name, Value, meter, false) -> add_handler(meter, Name), folsom_metrics_meter:mark(Name, Value), ok; notify(Name, Value, meter_reader, true) -> folsom_metrics_meter_reader:mark(Name, Value), ok; notify(Name, Value, meter_reader, false) -> add_handler(meter, Name), folsom_metrics_meter_reader:mark(Name, Value), ok; notify(Name, Value, duration, true) -> folsom_metrics_duration:update(Name, Value), ok; notify(Name, Value, duration, false) -> add_handler(duration, Name), folsom_metrics_duration:update(Name, Value), ok; notify(Name, Value, spiral, true) -> folsom_metrics_spiral:update(Name, Value), ok; notify(Name, Value, spiral, false) -> add_handler(spiral, Name), folsom_metrics_spiral:update(Name, Value), ok; notify(_, _, Type, _) -> {error, Type, unsupported_metric_type}. erlang-folsom-0.8.0+dfsg/src/folsom_ewma.erl000066400000000000000000000046521224324231000210060ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_ewma.erl %%% @author joe williams %%% @doc %%% based on https://github.com/codahale/metrics/blob/development/src/main/java/com/yammer/metrics/stats/EWMA.java %%% references: %%% http://www.teamquest.com/pdfs/whitepaper/ldavg1.pdf %%% http://www.teamquest.com/pdfs/whitepaper/ldavg2.pdf %%% @end %%%----------------------------------------------------------------- -module(folsom_ewma). -define(M1_ALPHA, 1 - math:exp(-5 / 60.0)). -define(M5_ALPHA, 1 - math:exp(-5 / 60.0 / 5)). -define(M15_ALPHA, 1 - math:exp(-5 / 60.0 / 15)). -define(D1_ALPHA, 1 - math:exp(-5 / 60.0 / 1440)). -record(ewma, { alpha, interval = 5, % seconds initialized = false, rate = 0, total = 0 }). -export([update/2, new/2, rate/1, tick/1, one_minute_ewma/0, five_minute_ewma/0, fifteen_minute_ewma/0, one_day_ewma/0]). % API one_minute_ewma() -> new(?M1_ALPHA, 5). five_minute_ewma() -> new(?M5_ALPHA, 5). fifteen_minute_ewma() -> new(?M15_ALPHA, 5). one_day_ewma() -> new(?D1_ALPHA, 5). new(Alpha, Interval) -> #ewma{alpha = Alpha, interval = Interval}. update(#ewma{total = Total} = EWMA, Value) -> EWMA#ewma{total = Total + Value}. tick(#ewma{total = Total, rate = Rate, initialized = Init, interval = Interval, alpha = Alpha} = EWMA) -> InstantRate = Total / Interval, Rate1 = rate_calc(Init, Alpha, Rate, InstantRate), EWMA#ewma{rate = Rate1, initialized = true, total = 0}. rate(#ewma{rate = Rate}) -> Rate. % Internal API rate_calc(true, Alpha, Rate, InstantRate) -> Rate1 = Rate + (Alpha * (InstantRate - Rate)), Rate1; rate_calc(false, _, _, InstantRate) -> InstantRate. erlang-folsom-0.8.0+dfsg/src/folsom_meter_timer_server.erl000066400000000000000000000137611224324231000237600ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_meter_timer_server.erl %%% @author joe williams %%% @doc %%% gen_server for registering meter ticks %%% @end %%%----------------------------------------------------------------- -module(folsom_meter_timer_server). -behaviour(gen_server). %% API -export([start_link/0, register/2, unregister/1, dump/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). -include("folsom.hrl"). -record(state, { registered_timers = [] }). %%%=================================================================== %%% API %%%=================================================================== %%-------------------------------------------------------------------- %% @doc %% Starts the server %% %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} %% @end %%-------------------------------------------------------------------- start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== %%-------------------------------------------------------------------- %% @private %% @doc %% Initializes the server %% %% @spec init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% @end %%-------------------------------------------------------------------- init([]) -> {ok, #state{}}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling call messages %% %% @spec handle_call(Request, From, State) -> %% {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_call({register, Name, Module}, _From, State) -> NewState = case proplists:is_defined(Name, State#state.registered_timers) of true -> State; false -> {ok, Ref} = timer:apply_interval(?DEFAULT_INTERVAL, Module, tick, [Name]), NewList = [{Name, Ref} | State#state.registered_timers], #state{registered_timers = NewList} end, {reply, ok, NewState}; handle_call({unregister, Name}, _From, State) -> NewState = case proplists:is_defined(Name, State#state.registered_timers) of true -> Ref = proplists:get_value(Name, State#state.registered_timers), {ok, cancel} = timer:cancel(Ref), #state{registered_timers = proplists:delete(Name, State#state.registered_timers)}; false -> State end, {reply, ok, NewState}; handle_call(dump, _From, State) -> {reply, State, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling cast messages %% %% @spec handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling all non call/cast messages %% %% @spec handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any %% necessary cleaning up. When it returns, the gen_server terminates %% with Reason. The return value is ignored. %% %% @spec terminate(Reason, State) -> void() %% @end %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% @private %% @doc %% Convert process state when code is changed %% %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} %% @end %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== %% Name of the metric and name of the module used to tick said metric register(Name, Module) -> gen_server:call(?SERVER, {register, Name, Module}). unregister(Name) -> gen_server:call(?SERVER, {unregister, Name}). dump() -> gen_server:call(?SERVER, dump). erlang-folsom-0.8.0+dfsg/src/folsom_metrics.erl000066400000000000000000000150651224324231000215230ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_metrics.erl %%% @author joe williams %%% @doc %%% @end %%%------------------------------------------------------------------ -module(folsom_metrics). -export([ new_counter/1, new_gauge/1, new_histogram/1, new_histogram/2, new_histogram/3, new_histogram/4, new_history/1, new_history/2, new_meter/1, new_meter_reader/1, new_duration/1, new_duration/2, new_duration/3, new_duration/4, new_spiral/1, delete_metric/1, tag_metric/2, untag_metric/2, notify/1, notify/2, notify/3, notify/4, safely_notify/1, safely_notify/2, safely_notify/3, safely_notify/4, notify_existing_metric/3, get_metrics/0, metric_exists/1, get_metrics_info/0, get_metrics_value/1, get_metrics_value/2, get_metric_info/1, get_metric_value/1, get_histogram_statistics/1, get_histogram_statistics/2, get_history_values/2, get_tags/1, histogram_timed_update/2, histogram_timed_update/3, histogram_timed_update/4, histogram_timed_begin/1, histogram_timed_notify/1, safely_histogram_timed_update/2, safely_histogram_timed_update/3, safely_histogram_timed_update/4, safely_histogram_timed_notify/1 ]). -include("folsom.hrl"). %% Metrics API new_counter(Name) -> folsom_ets:add_handler(counter, Name). new_gauge(Name) -> folsom_ets:add_handler(gauge, Name). new_histogram(Name) -> folsom_metrics:new_histogram(Name, ?DEFAULT_SAMPLE_TYPE, ?DEFAULT_SIZE, ?DEFAULT_ALPHA). new_histogram(Name, slide_uniform) -> folsom_metrics:new_histogram(Name, slide_uniform, {?DEFAULT_SLIDING_WINDOW, ?DEFAULT_SIZE}, ?DEFAULT_ALPHA); new_histogram(Name, SampleType) -> folsom_metrics:new_histogram(Name, SampleType, ?DEFAULT_SIZE, ?DEFAULT_ALPHA). new_histogram(Name, SampleType, SampleSize) -> folsom_metrics:new_histogram(Name, SampleType, SampleSize, ?DEFAULT_ALPHA). new_histogram(Name, SampleType, SampleSize, Alpha) -> folsom_ets:add_handler(histogram, Name, SampleType, SampleSize, Alpha). new_history(Name) -> folsom_metrics:new_history(Name, ?DEFAULT_SIZE). new_history(Name, SampleSize) -> folsom_ets:add_handler(history, Name, SampleSize). new_meter(Name) -> folsom_ets:add_handler(meter, Name). new_meter_reader(Name) -> folsom_ets:add_handler(meter_reader, Name). new_duration(Name) -> folsom_metrics:new_duration(Name, ?DEFAULT_SAMPLE_TYPE, ?DEFAULT_SIZE, ?DEFAULT_ALPHA). new_duration(Name, SampleType) -> folsom_metrics:new_duration(Name, SampleType, ?DEFAULT_SIZE, ?DEFAULT_ALPHA). new_duration(Name, SampleType, SampleSize) -> folsom_metrics:new_duration(Name, SampleType, SampleSize, ?DEFAULT_ALPHA). new_duration(Name, SampleType, SampleSize, Alpha) -> folsom_ets:add_handler(duration, Name, SampleType, SampleSize, Alpha). new_spiral(Name) -> folsom_ets:add_handler(spiral, Name). tag_metric(Name, Tag) -> folsom_ets:tag_handler(Name, Tag). untag_metric(Name, Tag) -> folsom_ets:untag_handler(Name, Tag). delete_metric(Name) -> folsom_ets:delete_handler(Name). notify(Event) -> folsom_ets:notify(Event). notify(Name, Event) -> folsom_ets:notify(Name, Event). notify(Name, Event, Type) -> folsom_ets:notify(Name, Event, Type). notify(Name, Event, Type, Tags) -> folsom_ets:tagged_notify(Name, Event, Type, Tags). safely_notify(Event) -> catch notify(Event). safely_notify(Name, Event) -> catch notify(Name, Event). safely_notify(Name, Event, Type) -> catch notify(Name, Event, Type). safely_notify(Name, Event, Type, Tags) -> catch notify(Name, Event, Type, Tags). notify_existing_metric(Name, Event, Type) -> folsom_ets:notify_existing_metric(Name, Event, Type). get_metrics() -> folsom_ets:get_handlers(). metric_exists(Name) -> folsom_ets:handler_exists(Name). get_metrics_info() -> folsom_ets:get_handlers_info(). get_metrics_value(Tag) -> folsom_ets:get_group_values(Tag). get_metrics_value(Tag, Type) -> folsom_ets:get_group_values(Tag, Type). get_metric_info(Name) -> [folsom_ets:get_info(Name)]. get_metric_value(Name) -> folsom_ets:get_values(Name). get_histogram_statistics(Name) -> Values = folsom_ets:get_values(Name), bear:get_statistics(Values). get_histogram_statistics(Name1, Name2) -> Values1 = get_metric_value(Name1), Values2 = get_metric_value(Name2), bear:get_statistics(Values1, Values2). get_history_values(Name, Count) -> folsom_ets:get_history_values(Name, Count). get_tags(Name) -> folsom_ets:get_tags(Name). histogram_timed_update(Name, Fun) -> {Time, Value} = timer:tc(Fun), ok = notify({Name, Time}), Value. histogram_timed_update(Name, Fun, Args) -> {Time, Value} = timer:tc(Fun, Args), ok = notify({Name, Time}), Value. histogram_timed_update(Name, Mod, Fun, Args) -> {Time, Value} = timer:tc(Mod, Fun, Args), ok = notify({Name, Time}), Value. histogram_timed_begin(Name) -> {Name, os:timestamp()}. histogram_timed_notify({Name, Begin}) -> Now = os:timestamp(), Time = timer:now_diff(Now, Begin), ok = notify({Name, Time}). safely_histogram_timed_update(Name, Fun) -> {Time, Value} = timer:tc(Fun), _ = safely_notify({Name, Time}), Value. safely_histogram_timed_update(Name, Fun, Args) -> {Time, Value} = timer:tc(Fun, Args), _ = safely_notify({Name, Time}), Value. safely_histogram_timed_update(Name, Mod, Fun, Args) -> {Time, Value} = timer:tc(Mod, Fun, Args), _ = safely_notify({Name, Time}), Value. safely_histogram_timed_notify({Name, Begin}) -> Now = os:timestamp(), Time = timer:now_diff(Now, Begin), _ = safely_notify({Name, Time}), ok. erlang-folsom-0.8.0+dfsg/src/folsom_metrics_counter.erl000066400000000000000000000035631224324231000232620ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_metrics_counter.erl %%% @author joe williams %%% @doc %%% @end %%%----------------------------------------------------------------- -module(folsom_metrics_counter). -export([new/1, inc/1, inc/2, dec/1, dec/2, get_value/1, clear/1, delete/1]). -define(WIDTH, 16). %% Keep this a power of two -include("folsom.hrl"). new(Name) -> Counters = [{{Name,N}, 0} || N <- lists:seq(0,?WIDTH-1)], ets:insert(?COUNTER_TABLE, Counters). inc(Name) -> ets:update_counter(?COUNTER_TABLE, key(Name), 1). inc(Name, Value) -> ets:update_counter(?COUNTER_TABLE, key(Name), Value). dec(Name) -> ets:update_counter(?COUNTER_TABLE, key(Name), -1). dec(Name, Value) -> ets:update_counter(?COUNTER_TABLE, key(Name), -Value). get_value(Name) -> Count = lists:sum(ets:select(?COUNTER_TABLE, [{{{Name,'_'},'$1'},[],['$1']}])), Count. clear(Name) -> new(Name). delete(Name) -> Counters = [{Name,N} || N <- lists:seq(0,?WIDTH-1)], [ets:delete(?COUNTER_TABLE, Counter) || Counter <- Counters], ok. key(Name) -> X = erlang:system_info(scheduler_id), Rnd = X band (?WIDTH-1), {Name, Rnd}. erlang-folsom-0.8.0+dfsg/src/folsom_metrics_duration.erl000066400000000000000000000052621224324231000234260ustar00rootroot00000000000000%% ------------------------------------------------------------------- %% %% Copyright (c) 2007-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you 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. %% %% ------------------------------------------------------------------- %%%------------------------------------------------------------------- %%% File: folsom_metrics_duration.erl %%% @author Russell Brown %%% @doc Tracks the time something takes. If %%% you can, use folsom_metrics:histogram_timed_update/2,3,4. %%% This is for the case when you can't wrap your timed action %%% in a fun. Calling timer_start / timer_end in the correct %%% order is the calling code's responsibility. %%% @end %%%------------------------------------------------------------------ -module(folsom_metrics_duration). -export([new/1, update/2, get_value/1, get_values/1 ]). -include("folsom.hrl"). new(Name) -> %% {Name, count, start, last} Dur = {Name, 0, undefined, 0}, ets:insert(?DURATION_TABLE, Dur). update(Name, timer_start) -> StartTime = os:timestamp(), ets:update_element(?DURATION_TABLE, Name, {3, StartTime}); update(Name, timer_end) -> EndTime = os:timestamp(), case ets:lookup_element(?DURATION_TABLE, Name, 3) of undefined -> ok; StartTime -> %% potential race, but then you're using it wrong ets:update_element(?DURATION_TABLE, Name, {3, undefined}), Duration = timer:now_diff(EndTime, StartTime), ets:update_counter(?DURATION_TABLE, Name, {2, 1}), ets:update_element(?DURATION_TABLE, Name, {4, Duration}), folsom_metrics_histogram:update(Name, Duration) end. % gets the duration tuple from ets get_value(Name) -> [Dur] = ets:lookup(?DURATION_TABLE, Name), Dur. % pulls the sample out of the record gotten from ets % and the duration get_values(Name) -> Values = folsom_metrics_histogram:get_values(Name), {Name, Cnt, _Start, Last} = get_value(Name), Stats = bear:get_statistics(Values), [{count, Cnt}, {last, Last} | Stats]. erlang-folsom-0.8.0+dfsg/src/folsom_metrics_gauge.erl000066400000000000000000000023601224324231000226650ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_metrics_gauge.erl %%% @author joe williams %%% @doc %%% @end %%%----------------------------------------------------------------- -module(folsom_metrics_gauge). -export([new/1, update/2, clear/1, get_value/1]). -include("folsom.hrl"). new(Name) -> Gauge = {Name, 0}, ets:insert(?GAUGE_TABLE, Gauge). update(Name, Value) -> Gauge = {Name, Value}, ets:insert(?GAUGE_TABLE, Gauge). clear(Name) -> new(Name). get_value(Name) -> [{_, Values}] = ets:lookup(?GAUGE_TABLE, Name), Values. erlang-folsom-0.8.0+dfsg/src/folsom_metrics_histogram.erl000066400000000000000000000046031224324231000235740ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_metrics_histogram.erl %%% @author joe williams %%% @doc %%% @end %%%------------------------------------------------------------------ -module(folsom_metrics_histogram). -export([new/1, new/2, new/3, new/4, update/2, get_value/1, get_values/1 ]). -include("folsom.hrl"). new(Name) -> new(Name, uniform). new(Name, slide) -> new(Name, slide, ?DEFAULT_SLIDING_WINDOW); new(Name, slide_uniform) -> new(Name, slide_uniform, {?DEFAULT_SLIDING_WINDOW, ?DEFAULT_SIZE}); new(Name, SampleType) -> new(Name, SampleType, ?DEFAULT_SIZE). new(Name, SampleType, SampleSize) -> Sample = folsom_sample:new(SampleType, SampleSize), Hist = #histogram{type = SampleType, sample = Sample}, ets:insert(?HISTOGRAM_TABLE, {Name, Hist}). new(Name, SampleType, SampleSize, Alpha) -> Sample = folsom_sample:new(SampleType, SampleSize, Alpha), Hist = #histogram{type = SampleType, sample = Sample}, ets:insert(?HISTOGRAM_TABLE, {Name, Hist}). update(Name, Value) -> Hist = get_value(Name), Sample = Hist#histogram.sample, case folsom_sample:update(Hist#histogram.type, Hist#histogram.sample, Value) of Sample -> %% sample didn't change, don't need to write it back true; NewSample -> ets:insert(?HISTOGRAM_TABLE, {Name, Hist#histogram{sample = NewSample}}) end. % gets the histogram record from ets get_value(Name) -> [{_, Value}] = ets:lookup(?HISTOGRAM_TABLE, Name), Value. % pulls the sample out of the record gotten from ets get_values(Name) -> Hist = get_value(Name), folsom_sample:get_values(Hist#histogram.type, Hist#histogram.sample). erlang-folsom-0.8.0+dfsg/src/folsom_metrics_histogram_ets.erl000066400000000000000000000116001224324231000244420ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_metrics_histogram_ets.erl %%% @author joe williams %%% @doc %%% this module creates ets tables for histograms as to be the parent %%% process for those histograms so the apps using folsom dont have to %%% @end %%%------------------------------------------------------------------ -module(folsom_metrics_histogram_ets). -behaviour(gen_server). %% API -export([start_link/0, new/2]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). -record(state, {}). %%%=================================================================== %%% API %%%=================================================================== %%-------------------------------------------------------------------- %% @doc %% Starts the server %% %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} %% @end %%-------------------------------------------------------------------- start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== %%-------------------------------------------------------------------- %% @private %% @doc %% Initializes the server %% %% @spec init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% @end %%-------------------------------------------------------------------- init([]) -> {ok, #state{}}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling call messages %% %% @spec handle_call(Request, From, State) -> %% {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_call({new, {Table, Opts}}, _From, State) -> Reply = ets:new(Table, Opts), {reply, Reply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling cast messages %% %% @spec handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling all non call/cast messages %% %% @spec handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any %% necessary cleaning up. When it returns, the gen_server terminates %% with Reason. The return value is ignored. %% %% @spec terminate(Reason, State) -> void() %% @end %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% @private %% @doc %% Convert process state when code is changed %% %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} %% @end %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== new(Table, Opts) -> gen_server:call(?SERVER, {new, {Table, Opts}}). erlang-folsom-0.8.0+dfsg/src/folsom_metrics_history.erl000066400000000000000000000045711224324231000233040ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_metrics_history.erl %%% @author joe williams %%% @doc %%% @end %%%------------------------------------------------------------------ -module(folsom_metrics_history). -export([new/1, update/3, get_events/1, get_events/2, get_value/1 ]). -include("folsom.hrl"). -define(ETSOPTS, [ ordered_set, public, {write_concurrency, true} ]). new(Name) -> Tid = ets:new(history, ?ETSOPTS), ets:insert(?HISTORY_TABLE, {Name, #history{tid=Tid}}), ok. update(Name, Size, Value) -> #history{tid=Tid} = get_value(Name), Key = folsom_utils:now_epoch_micro(), insert(Tid, Key, Size, Value, ets:info(Tid, size)). get_value(Name) -> [{_, Value}] = ets:lookup(?HISTORY_TABLE, Name), Value. get_events(Name) -> get_events(Name, ?DEFAULT_LIMIT). get_events(Name, Count) -> #history{tid=Tid} = get_value(Name), get_last_events(Tid, Count). % Internal API insert(Name, Key, Size, Value, Count) when Count < Size -> true = ets:insert(Name, {Key, [{event, Value}]}); insert(Name, Key, _, Value, _) -> FirstKey = ets:first(Name), true = ets:delete(Name, FirstKey), true = ets:insert(Name, {Key, [{event, Value}]}). get_last_events(Name, Count) -> LastKey = ets:last(Name), get_prev_event(Name, LastKey, Count, []). % get_prev_event/4 used by get_last_events/2 get_prev_event(_, '$end_of_table', _, Acc) -> Acc; get_prev_event(Name, Key, Count, Acc) when length(Acc) < Count -> Event = ets:lookup(Name, Key), get_prev_event(Name, ets:prev(Name, Key), Count, lists:append(Acc, Event)); get_prev_event(_, _, _, Acc) -> Acc. erlang-folsom-0.8.0+dfsg/src/folsom_metrics_meter.erl000066400000000000000000000106451224324231000227160ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_metrics_meter.erl %%% @author joe williams %%% @doc %%% @end %%%------------------------------------------------------------------ -module(folsom_metrics_meter). -export([new/1, tick/1, mark/1, mark/2, get_values/1, get_acceleration/1 ]). -record(meter, { one, five, fifteen, day, count = 0, start_time }). -include("folsom.hrl"). new(Name) -> OneMin = folsom_ewma:one_minute_ewma(), FiveMin = folsom_ewma:five_minute_ewma(), FifteenMin = folsom_ewma:fifteen_minute_ewma(), OneDay = folsom_ewma:one_day_ewma(), ets:insert(?METER_TABLE, {Name, #meter{one = OneMin, five = FiveMin, fifteen = FifteenMin, day = OneDay, start_time = folsom_utils:now_epoch_micro()}}). tick(Name) -> #meter{one = OneMin, five = FiveMin, fifteen = FifteenMin, day = OneDay} = Meter = get_value(Name), OneMin1 = folsom_ewma:tick(OneMin), FiveMin1 = folsom_ewma:tick(FiveMin), FifteenMin1 = folsom_ewma:tick(FifteenMin), OneDay1 = folsom_ewma:tick(OneDay), ets:insert(?METER_TABLE, {Name, Meter#meter{one = OneMin1, five = FiveMin1, fifteen = FifteenMin1, day = OneDay1}}). mark(Name) -> mark(Name, 1). mark(Name, Value) -> #meter{count = Count, one = OneMin, five = FiveMin, fifteen = FifteenMin, day = OneDay} = Meter = get_value(Name), OneMin1 = folsom_ewma:update(OneMin, Value), FiveMin1 = folsom_ewma:update(FiveMin, Value), FifteenMin1 = folsom_ewma:update(FifteenMin, Value), OneDay1 = folsom_ewma:update(OneDay, Value), ets:insert(?METER_TABLE, {Name, Meter#meter{count = Count + Value, one = OneMin1, five = FiveMin1, fifteen = FifteenMin1, day = OneDay1}}). get_values(Name) -> #meter{one = OneMin, five = FiveMin, fifteen = FifteenMin, day = OneDay, count = Count} = Meter = get_value(Name), L = [ {count, Count}, {one, get_rate(OneMin)}, {five, get_rate(FiveMin)}, {fifteen, get_rate(FifteenMin)}, {day, get_rate(OneDay)}, {mean, get_mean_rate(Meter)}, {acceleration, get_acceleration(Name)} ], [ {K,V} || {K,V} <- L, V /= undefined ]. get_acceleration(Name) -> #meter{one = OneMin, five = FiveMin, fifteen = FifteenMin} = get_value(Name), [ {one_to_five, calc_acceleration(get_rate(OneMin), get_rate(FiveMin), 300)}, {five_to_fifteen, calc_acceleration(get_rate(FiveMin), get_rate(FifteenMin), 600)}, {one_to_fifteen, calc_acceleration(get_rate(OneMin), get_rate(FifteenMin), 900)} ]. % internal functions get_rate(EWMA) -> folsom_ewma:rate(EWMA). get_mean_rate(#meter{count = Count, start_time = Start}) -> calc_mean_rate(Start, Count). get_value(Name) -> [{_, Value}] = ets:lookup(?METER_TABLE, Name), Value. calc_mean_rate(_, 0) -> 0.0; calc_mean_rate(Start, Count) -> Elapsed = folsom_utils:now_epoch_micro() - Start, Count / Elapsed. calc_acceleration(Rate1, Rate2, Interval) -> % most current velocity minus previous velocity get_rate(Rate1, Rate2, Interval). get_rate(Value1, Value2, Interval) -> Delta = Value1 - Value2, Delta / Interval. erlang-folsom-0.8.0+dfsg/src/folsom_metrics_meter_reader.erl000066400000000000000000000113041224324231000242310ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% Copyright 2011, Opscode %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_metrics_meter_reader.erl %%% @author Seth Falcon %%% @author joe williams %%% @doc %%% @end %%%------------------------------------------------------------------ -module(folsom_metrics_meter_reader). -export([new/1, tick/1, mark/1, mark/2, get_values/1, get_acceleration/1 ]). -record(meter_reader, { one, five, fifteen, count = 0, start_time, last_count = unset }). -include("folsom.hrl"). new(Name) -> OneMin = folsom_ewma:one_minute_ewma(), FiveMin = folsom_ewma:five_minute_ewma(), FifteenMin = folsom_ewma:fifteen_minute_ewma(), ets:insert(?METER_READER_TABLE, {Name, #meter_reader{one = OneMin, five = FiveMin, fifteen = FifteenMin, start_time = folsom_utils:now_epoch_micro()}}). tick(Name) -> #meter_reader{one = OneMin, five = FiveMin, fifteen = FifteenMin} = Meter = get_value(Name), OneMin1 = folsom_ewma:tick(OneMin), FiveMin1 = folsom_ewma:tick(FiveMin), FifteenMin1 = folsom_ewma:tick(FifteenMin), ets:insert(?METER_READER_TABLE, {Name, Meter#meter_reader{one = OneMin1, five = FiveMin1, fifteen = FifteenMin1}}). mark(Name) -> mark(Name, 1). mark(Name, Value) -> % skip first reading to bootstrap last value #meter_reader{count = Count, last_count = LastCount, one = OneMin, five = FiveMin, fifteen = FifteenMin} = Meter = get_value(Name), NewMeter = case LastCount of unset -> Meter#meter_reader{last_count = Value}; _ -> Delta = Value - LastCount, OneMin1 = folsom_ewma:update(OneMin, Delta), FiveMin1 = folsom_ewma:update(FiveMin, Delta), FifteenMin1 = folsom_ewma:update(FifteenMin, Delta), Meter#meter_reader{count = Count + Delta, last_count = Value, one = OneMin1, five = FiveMin1, fifteen = FifteenMin1} end, ets:insert(?METER_READER_TABLE, {Name, NewMeter}). get_values(Name) -> #meter_reader{one = OneMin, five = FiveMin, fifteen = FifteenMin} = Meter = get_value(Name), L = [ {one, get_rate(OneMin)}, {five, get_rate(FiveMin)}, {fifteen, get_rate(FifteenMin)}, {mean, get_mean_rate(Meter)}, {acceleration, get_acceleration(Name)} ], [ {K,V} || {K,V} <- L, V /= undefined ]. get_acceleration(Name) -> #meter_reader{one = OneMin, five = FiveMin, fifteen = FifteenMin} = get_value(Name), [ {one_to_five, calc_acceleration(get_rate(OneMin), get_rate(FiveMin), 300)}, {five_to_fifteen, calc_acceleration(get_rate(FiveMin), get_rate(FifteenMin), 600)}, {one_to_fifteen, calc_acceleration(get_rate(OneMin), get_rate(FifteenMin), 900)} ]. % internal functions get_rate(EWMA) -> folsom_ewma:rate(EWMA). get_mean_rate(#meter_reader{count = Count, start_time = Start}) -> calc_mean_rate(Start, Count). get_value(Name) -> [{_, Value}] = ets:lookup(?METER_READER_TABLE, Name), Value. calc_mean_rate(_, 0) -> 0.0; calc_mean_rate(Start, Count) -> Elapsed = folsom_utils:now_epoch_micro() - Start, Count / Elapsed. calc_acceleration(Rate1, Rate2, Interval) -> % most current velocity minus previous velocity get_rate(Rate1, Rate2, Interval). get_rate(Value1, Value2, Interval) -> Delta = Value1 - Value2, Delta / Interval. erlang-folsom-0.8.0+dfsg/src/folsom_metrics_spiral.erl000066400000000000000000000047661224324231000231030ustar00rootroot00000000000000%%% %%% Copyright 2012, Basho Technologies, Inc. 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_metrics_spiral.erl %%% @author Russell Brown %%% @doc A total count, and sliding window count of events over the last %%% minute. %%% @end %%%------------------------------------------------------------------ -module(folsom_metrics_spiral). -export([new/1, update/2, trim/2, get_value/1, get_values/1 ]). %% size of the window in seconds -define(WINDOW, 60). -define(WIDTH, 16). %% Keep this a power of two -include("folsom.hrl"). new(Name) -> Spiral = #spiral{}, Pid = folsom_sample_slide_sup:start_slide_server(?MODULE, Spiral#spiral.tid, ?WINDOW), ets:insert_new(Spiral#spiral.tid, [{{count, N}, 0} || N <- lists:seq(0,?WIDTH-1)]), ets:insert(?SPIRAL_TABLE, {Name, Spiral#spiral{server=Pid}}). update(Name, Value) -> #spiral{tid=Tid} = get_value(Name), Moment = folsom_utils:now_epoch(), X = erlang:system_info(scheduler_id), Rnd = X band (?WIDTH-1), folsom_utils:update_counter(Tid, {Moment, Rnd}, Value), ets:update_counter(Tid, {count, Rnd}, Value). get_value(Name) -> [{Name, Spiral}] = ets:lookup(?SPIRAL_TABLE, Name), Spiral. trim(Tid, _Window) -> Oldest = oldest(), ets:select_delete(Tid, [{{{'$1','_'},'_'}, [{is_integer, '$1'}, {'<', '$1', Oldest}], ['true']}]). get_values(Name) -> Oldest = oldest(), #spiral{tid=Tid} = get_value(Name), Count = lists:sum(ets:select(Tid, [{{{count,'_'},'$1'},[],['$1']}])), One = lists:sum(ets:select(Tid, [{{{'$1','_'},'$2'},[{is_integer, '$1'}, {'>=', '$1', Oldest}],['$2']}])), [{count, Count}, {one, One}]. oldest() -> folsom_utils:now_epoch() - ?WINDOW. erlang-folsom-0.8.0+dfsg/src/folsom_sample.erl000066400000000000000000000051311224324231000213270ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_sample.erl %%% @author joe williams %%% @doc %%% @end %%%------------------------------------------------------------------ -module(folsom_sample). -export([ new/1, new/2, new/3, update/3, get_values/2 ]). -include("folsom.hrl"). %% API new(slide) -> new(slide, ?DEFAULT_SLIDING_WINDOW); new(slide_uniform) -> new(slide_uniform, {?DEFAULT_SLIDING_WINDOW, ?DEFAULT_SIZE}); new(Type) -> new(Type, ?DEFAULT_SIZE, ?DEFAULT_ALPHA). new(Type, Size) -> new(Type, Size, ?DEFAULT_ALPHA). new(slide, Size, _) -> folsom_sample_slide:new(Size); new(slide_uniform, Sizes, _) -> folsom_sample_slide_uniform:new(Sizes); new(uniform, Size, _) -> folsom_sample_uniform:new(Size); new(none, Size, _) -> folsom_sample_none:new(Size); new(slide_sorted, Size, _) -> folsom_sample_slide_sorted:new(Size); new(exdec, Size, Alpha) -> folsom_sample_exdec:new(Size, Alpha). update(uniform, Sample, Value) -> folsom_sample_uniform:update(Sample, Value); update(none, Sample, Value) -> folsom_sample_none:update(Sample, Value); update(slide_sorted, Sample, Value) -> folsom_sample_slide_sorted:update(Sample, Value); update(exdec, Sample, Value) -> folsom_sample_exdec:update(Sample, Value); update(slide, Sample, Value) -> folsom_sample_slide:update(Sample, Value); update(slide_uniform, Sample, Value) -> folsom_sample_slide_uniform:update(Sample, Value). get_values(uniform, Sample) -> folsom_sample_uniform:get_values(Sample); get_values(none, Sample) -> folsom_sample_none:get_values(Sample); get_values(slide_sorted, Sample) -> folsom_sample_slide_sorted:get_values(Sample); get_values(exdec, Sample) -> folsom_sample_exdec:get_values(Sample); get_values(slide, Sample) -> folsom_sample_slide:get_values(Sample); get_values(slide_uniform, Sample) -> folsom_sample_slide_uniform:get_values(Sample). erlang-folsom-0.8.0+dfsg/src/folsom_sample_exdec.erl000066400000000000000000000114601224324231000225010ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_sample_exdec.erl %%% @author joe williams %%% @doc %%% erlang implementation of a exponentially-decaying random sample %%% based on a java implementation by coda hale, which can be found at: %%% %%% https://github.com/codahale/metrics/blob/development/src/main/java/com/yammer/metrics/core/ExponentiallyDecayingSample.java %%% %%% that implementation is based on: %%% %%% http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf %%% @end %%%------------------------------------------------------------------ -module(folsom_sample_exdec). -export([ new/2, update/2, get_values/1 ]). -define(HOURSECS, 3600). -include("folsom.hrl"). new(Size, Alpha) -> Now = folsom_utils:now_epoch(), #exdec{start = Now, next = Now + ?HOURSECS, alpha = Alpha, size = Size}. update(#exdec{reservoir = Reservoir, alpha = Alpha, start = Start, next = Next} = Sample, Value) -> Timestamp = folsom_utils:now_epoch(), % immediately see if we need to rescale {NewRes, NewStart, NewNext} = rescale(Reservoir, Timestamp, Next, Start, Alpha), % now lets update the sample if the new value is worthy update(Sample#exdec{reservoir = NewRes, next = NewNext, start = NewStart}, Value, Timestamp). get_values(#exdec{reservoir = Reservoir}) -> {_, Values} = lists:unzip(ets:tab2list(Reservoir)), Values. % internal api update(#exdec{reservoir = Reservoir, alpha = Alpha, start = Start, n = N, size = Size, seed = Seed} = Sample, Value, Timestamp) when N =< Size -> % since N is =< Size we can just add the new value to the sample {Rand, New_seed} = random:uniform_s(N, Seed), Priority = priority(Alpha, Timestamp, Start, Rand), true = ets:insert(Reservoir, {Priority, Value}), Sample#exdec{n = folsom_utils:get_ets_size(Reservoir), seed = New_seed}; update(#exdec{reservoir = Reservoir, alpha = Alpha, start = Start, n = N, seed = Seed} = Sample, Value, Timestamp) -> % when N is not =< Size we need to check to see if the priority of % the new value is greater than the first (smallest) existing priority {Rand, NewSeed} = random:uniform_s(N, Seed), Priority = priority(Alpha, Timestamp, Start, Rand), First = ets:first(Reservoir), update_on_priority(Sample, First, Priority, NewSeed, Value). update_on_priority(#exdec{reservoir = Reservoir} = Sample, First, Priority, NewSeed, Value) when First < Priority -> true = case ets:insert_new(Reservoir, {Priority, Value}) of true -> % priority didnt already exist, so we created it and need to delete the first one ets:delete(Reservoir, First); false -> % priority existed, we dont need to do anything true end, Sample#exdec{n = folsom_utils:get_ets_size(Reservoir), seed = NewSeed}; update_on_priority(Sample, _, _, _, _) -> Sample. % gaurd against a possible bug, T should always be =< ?HOURSECS % also to prevent overflow issues make sure alpha is always =< % math:log(1.79769313486231570815e+308) / 3599 = 0.19721664709457737 weight(Alpha, T) when T =< ?HOURSECS, Alpha =< 0.19721664709457737 -> math:exp(Alpha * T). priority(Alpha, Time, Start, Rand) -> weight(Alpha, Time - Start) / Rand. rescale(Reservoir, Now, Next, OldStart, Alpha) when Now >= Next -> NewStart = Now + ?HOURSECS, NewRes = delete_and_rescale(Reservoir, NewStart, OldStart, Alpha), {NewRes, Now, NewStart}; rescale(Reservoir, _, Next, Start, _) -> {Reservoir, Start, Next}. delete_and_rescale(Reservoir, NewStart, OldStart, Alpha) -> % get the existing reservoir ResList = ets:tab2list(Reservoir), % create a new ets table to use NewRes = folsom_metrics_histogram_ets:new(folsom_exdec,[ordered_set, {write_concurrency, true}, public]), % populate it with new priorities and the existing values [true = ets:insert(NewRes, {recalc_priority(Priority, Alpha, NewStart, OldStart) ,Value}) || {Priority, Value} <- ResList], % delete the old ets table true = ets:delete(Reservoir), % return the new ets table NewRes. recalc_priority(Priority, Alpha, Start, OldStart) -> Priority * math:exp(-Alpha * (Start - OldStart)). erlang-folsom-0.8.0+dfsg/src/folsom_sample_none.erl000066400000000000000000000027001224324231000223450ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_sample_none.erl %%% @author joe williams %%% @doc %%% no sampling, just a capped circular buffer %%% @end %%%----------------------------------------------------------------- -module(folsom_sample_none). -export([ new/1, update/2, get_values/1 ]). -include("folsom.hrl"). new(Size) -> #none{size = Size}. update(#none{size = Size, reservoir = Reservoir, n = N} = Sample, Value) when N =:= Size -> ets:insert(Reservoir, {N, Value}), Sample#none{n = 1}; update(#none{reservoir = Reservoir, n = N} = Sample, Value) -> ets:insert(Reservoir, {N, Value}), Sample#none{n = N + 1}. get_values(#none{reservoir = Reservoir}) -> {_, Values} = lists:unzip(ets:tab2list(Reservoir)), Values. erlang-folsom-0.8.0+dfsg/src/folsom_sample_slide.erl000066400000000000000000000036401224324231000225120ustar00rootroot00000000000000%%% %%% Copyright 2012, Basho Technologies, Inc. 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_sample_slide.erl %%% @author Russell Brown %%% @doc %%% Sliding window sample. Last Window seconds readings are recorded. %%% @end %%%----------------------------------------------------------------- -module(folsom_sample_slide). -export([ new/1, update/2, get_values/1, moment/0, trim/2 ]). -include("folsom.hrl"). -define(WIDTH, 16). %% Keep this a power of two new(Size) -> Sample = #slide{window = Size}, Pid = folsom_sample_slide_sup:start_slide_server(?MODULE, Sample#slide.reservoir, Sample#slide.window), Sample#slide{server=Pid}. update(#slide{reservoir = Reservoir} = Sample, Value) -> Moment = moment(), X = erlang:system_info(scheduler_id), Rnd = X band (?WIDTH-1), ets:insert(Reservoir, {{Moment, Rnd}, Value}), Sample. get_values(#slide{window = Window, reservoir = Reservoir}) -> Oldest = moment() - Window, ets:select(Reservoir, [{{{'$1','_'},'$2'},[{'>=', '$1', Oldest}],['$2']}]). moment() -> folsom_utils:now_epoch(). trim(Reservoir, Window) -> Oldest = moment() - Window, ets:select_delete(Reservoir, [{{{'$1','_'},'_'},[{'<', '$1', Oldest}],['true']}]). erlang-folsom-0.8.0+dfsg/src/folsom_sample_slide_server.erl000066400000000000000000000044761224324231000241100ustar00rootroot00000000000000%% ------------------------------------------------------------------- %% %% Copyright (c) 2011 Basho Technologies, Inc. %% %% This file is provided to you 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. %% %% ------------------------------------------------------------------- %%%------------------------------------------------------------------- %%% File: folsom_sample_slide_server.erl %%% @author Russell Brown %%% @doc %%% Serialization point for folsom_sample_slide. Handles %%% pruning of older smaples. One started per histogram. %%% See folsom.hrl, folsom_sample_slide, folsom_sample_slide_sup %%% @end %%%----------------------------------------------------------------- -module(folsom_sample_slide_server). -behaviour(gen_server). %% API -export([start_link/3, stop/1]). -record(state, {sample_mod, reservoir, window}). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). start_link(SampleMod, Reservoir, Window) -> gen_server:start_link(?MODULE, [SampleMod, Reservoir, Window], []). stop(Pid) -> gen_server:cast(Pid, stop). init([SampleMod, Reservoir, Window]) -> {ok, #state{sample_mod = SampleMod, reservoir = Reservoir, window = Window}, timeout(Window)}. handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. handle_cast(stop, State) -> {stop, normal, State}; handle_cast(_Msg, State) -> {noreply, State}. handle_info(timeout, State=#state{sample_mod = SampleMod, reservoir = Reservoir, window = Window}) -> SampleMod:trim(Reservoir, Window), {noreply, State, timeout(Window)}; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. timeout(Window) -> timer:seconds(Window) div 2. erlang-folsom-0.8.0+dfsg/src/folsom_sample_slide_sorted.erl000066400000000000000000000031621224324231000240710ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_sample_slide_sorted.erl %%% @author Ramon Lastres %%% @doc %%% simple sliding window histogram. %%% @end %%%----------------------------------------------------------------- -module(folsom_sample_slide_sorted). -export([ new/1, update/2, get_values/1 ]). -include("folsom.hrl"). new(Size) -> #slide_sorted{size = Size}. update(#slide_sorted{size = Size, reservoir = Reservoir, n = N} = Sample, Value) when N < Size -> ets:insert(Reservoir, {os:timestamp(), Value}), Sample#slide_sorted{n = N + 1}; update(#slide_sorted{reservoir = Reservoir, n = N, size = Size} = Sample, Value) when N == Size -> Oldest = ets:first(Reservoir), ets:delete(Reservoir, Oldest), ets:insert(Reservoir, {os:timestamp(), Value}), Sample. get_values(#slide_sorted{reservoir = Reservoir}) -> {_, Values} = lists:unzip(ets:tab2list(Reservoir)), Values. erlang-folsom-0.8.0+dfsg/src/folsom_sample_slide_sup.erl000066400000000000000000000032701224324231000234000ustar00rootroot00000000000000%% ------------------------------------------------------------------- %% %% Copyright (c) 2011 Basho Technologies, Inc. %% %% This file is provided to you 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. %% %% ------------------------------------------------------------------- %%%------------------------------------------------------------------- %%% File: folsom_sample_slide_sup.erl %%% @author Russell Brown %%% @doc %%% Starts simple_one_for_one children per slide sample %%% @end %%%----------------------------------------------------------------- -module(folsom_sample_slide_sup). -behaviour(supervisor). %% beahvior functions -export([start_link/0, init/1 ]). %% public functions -export([start_slide_server/3]). start_link () -> supervisor:start_link({local,?MODULE},?MODULE,[]). start_slide_server(SampleMod, Reservoir, Window) -> {ok, Pid} = supervisor:start_child(?MODULE, [SampleMod, Reservoir, Window]), Pid. %% @private init ([]) -> {ok,{{simple_one_for_one, 3, 180}, [ {undefined, {folsom_sample_slide_server, start_link, []}, transient, brutal_kill, worker, [folsom_sample_slide_server]} ]}}. erlang-folsom-0.8.0+dfsg/src/folsom_sample_slide_uniform.erl000066400000000000000000000051521224324231000242510ustar00rootroot00000000000000%%% %%% Copyright 2012, Basho Technologies, Inc. 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_sample_slide.erl %%% @author Russell Brown %%% @doc %%% Sliding window sample. Last Window seconds readings are recorded. %%% @end %%%----------------------------------------------------------------- -module(folsom_sample_slide_uniform). -export([ new/1, update/2, get_values/1, moment/0, trim/2 ]). -include("folsom.hrl"). new({Window, SampleSize}) -> Sample = #slide_uniform{window = Window, size = SampleSize}, Pid = folsom_sample_slide_sup:start_slide_server(?MODULE, Sample#slide_uniform.reservoir, Sample#slide_uniform.window), Sample#slide_uniform{server=Pid}. update(#slide_uniform{reservoir = Reservoir, size = Size} = Sample0, Value) -> Now = folsom_utils:timestamp(), Moment = folsom_utils:now_epoch(Now), MCnt = folsom_utils:update_counter(Reservoir, Moment, 1), Sample = case MCnt > Size of true -> {Rnd, _NewSeed} = random:uniform_s(MCnt, Now), maybe_update(Reservoir, {{Moment, Rnd}, Value}, Size), Sample0; false -> ets:insert(Reservoir, {{Moment, MCnt}, Value}), Sample0 end, Sample. maybe_update(Reservoir, {{_Moment, Rnd}, _Value}=Obj, Size) when Rnd =< Size -> ets:insert(Reservoir, Obj); maybe_update(_Reservoir, _Obj, _Size) -> ok. get_values(#slide_uniform{window = Window, reservoir = Reservoir}) -> Oldest = moment() - Window, ets:select(Reservoir, [{{{'$1', '_'},'$2'},[{'>=', '$1', Oldest}],['$2']}]). moment() -> folsom_utils:now_epoch(). trim(Reservoir, Window) -> Oldest = moment() - Window, ets:select_delete(Reservoir, [{{{'$1', '_'},'_'},[{'<', '$1', Oldest}],['true']}]), %% and trim the counters ets:select_delete(Reservoir, [{{'$1','_'},[{is_integer, '$1'}, {'<', '$1', Oldest}],['true']}]). erlang-folsom-0.8.0+dfsg/src/folsom_sample_uniform.erl000066400000000000000000000040241224324231000230660ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_sample_uniform.erl %%% @author joe williams %%% @doc %%% erlang implementation of a uniform random sample %%% based on a java implementation by coda hale, which can be found at: %%% %%% https://github.com/codahale/metrics/blob/development/src/main/java/com/yammer/metrics/core/UniformSample.java %%% %%% that implementation is based on algorithm R in: %%% %%% http://www.cs.umd.edu/~samir/498/vitter.pdf %%% @end %%%----------------------------------------------------------------- -module(folsom_sample_uniform). -export([ new/1, update/2, get_values/1 ]). -include("folsom.hrl"). new(Size) -> #uniform{size = Size}. update(#uniform{size = Size, reservoir = Reservoir, n = N} = Sample, Value) when N =< Size -> ets:insert(Reservoir, {N, Value}), Sample#uniform{n = N + 1}; update(#uniform{reservoir = Reservoir, size = Size, n = N, seed = Seed} = Sample, Value) -> {Rnd, New_seed} = random:uniform_s(N, Seed), maybe_update(Rnd, Size, Value, Reservoir), Sample#uniform{n = N + 1, seed = New_seed}. get_values(#uniform{reservoir = Reservoir}) -> {_, Values} = lists:unzip(ets:tab2list(Reservoir)), Values. maybe_update(Rnd, Size, Value, Reservoir) when Rnd < Size -> ets:insert(Reservoir, {Rnd, Value}); maybe_update(_Rnd, _Size, _Value, _Reservoir) -> ok. erlang-folsom-0.8.0+dfsg/src/folsom_sup.erl000066400000000000000000000102461224324231000206600ustar00rootroot00000000000000%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_sup.erl %%% @author joe williams %%% @doc %%% %%% @end %%%------------------------------------------------------------------- -module(folsom_sup). -behaviour(supervisor). %% API -export([start_link/0, create_tables/0]). %% Supervisor callbacks -export([init/1]). -include("folsom.hrl"). -define(SERVER, ?MODULE). %%%=================================================================== %%% API functions %%%=================================================================== %%-------------------------------------------------------------------- %% @doc %% Starts the supervisor %% %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} %% @end %%-------------------------------------------------------------------- start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). %%%=================================================================== %%% Supervisor callbacks %%%=================================================================== %%-------------------------------------------------------------------- %% @private %% @doc %% Whenever a supervisor is started using supervisor:start_link/[2,3], %% this function is called by the new process to find out about %% restart strategy, maximum restart frequency and child %% specifications. %% %% @spec init(Args) -> {ok, {SupFlags, [ChildSpec]}} | %% ignore | %% {error, Reason} %% @end %%-------------------------------------------------------------------- init([]) -> create_tables(), RestartStrategy = one_for_one, MaxRestarts = 1000, MaxSecondsBetweenRestarts = 3600, SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, Restart = permanent, Shutdown = 2000, Type = worker, TimerServer = {folsom_meter_timer_server, {folsom_meter_timer_server, start_link, []}, Restart, Shutdown, Type, [folsom_meter_timer_server]}, HistETSServer = {folsom_metrics_histogram_ets, {folsom_metrics_histogram_ets, start_link, []}, Restart, Shutdown, Type, [folsom_metrics_histogram_ets]}, SlideSup = {folsom_sample_slide_sup, {folsom_sample_slide_sup, start_link, []}, permanent, 5000, supervisor, [folsom_sample_slide_sup]}, {ok, {SupFlags, [SlideSup, TimerServer, HistETSServer]}}. %%%=================================================================== %%% Internal functions %%%=================================================================== create_tables() -> Tables = [ {?FOLSOM_TABLE, [set, named_table, public, {read_concurrency, true}]}, {?COUNTER_TABLE, [set, named_table, public, {write_concurrency, true}]}, {?GAUGE_TABLE, [set, named_table, public, {write_concurrency, true}]}, {?HISTOGRAM_TABLE, [set, named_table, public, {write_concurrency, true}]}, {?METER_TABLE, [set, named_table, public, {write_concurrency, true}]}, {?METER_READER_TABLE, [set, named_table, public, {write_concurrency, true}]}, {?HISTORY_TABLE, [set, named_table, public, {write_concurrency, true}]}, {?DURATION_TABLE, [ordered_set, named_table, public, {write_concurrency, true}]}, {?SPIRAL_TABLE, [set, named_table, public, {write_concurrency, true}]} ], [maybe_create_table(ets:info(Name), Name, Opts) || {Name, Opts} <- Tables], ok. maybe_create_table(undefined, Name, Opts) -> ets:new(Name, Opts); maybe_create_table(_, _, _) -> ok. erlang-folsom-0.8.0+dfsg/src/folsom_utils.erl000066400000000000000000000044741224324231000212170ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_utils.erl %%% @author joe williams %%% @doc %%% various util functions %%% @end %%%------------------------------------------------------------------ -module(folsom_utils). -export([ to_atom/1, convert_tags/1, now_epoch/0, now_epoch/1, now_epoch_micro/0, timestamp/0, get_ets_size/1, update_counter/3 ]). to_atom(Binary) when is_binary(Binary) -> list_to_atom(binary_to_list(Binary)); to_atom(List) when is_list(List) -> list_to_atom(List). convert_tags(Tags) -> [to_atom(Tag) || Tag <- Tags]. now_epoch() -> now_epoch(os:timestamp()). now_epoch({Mega, Sec, _}) -> (Mega * 1000000 + Sec). now_epoch_micro() -> {Mega, Sec, Micro} = os:timestamp(), (Mega * 1000000 + Sec) * 1000000 + Micro. %% useful because you can't meck os:timestamp for some reason timestamp() -> os:timestamp(). get_ets_size(Tab) -> ets:info(Tab, size). %% @doc %% Same as {@link ets:update_counter/3} but inserts `{Key, Value}' if object %% is missing in the table. update_counter(Tid, Key, Value) when is_integer(Value) -> %% try to update the counter, will badarg if it doesn't exist try ets:update_counter(Tid, Key, Value) of Res -> Res catch error:badarg -> %% row didn't exist, create it %% use insert_new to avoid races case ets:insert_new(Tid, {Key, Value}) of true -> Value; false -> %% someone beat us to it ets:update_counter(Tid, Key, Value) end end. erlang-folsom-0.8.0+dfsg/src/folsom_vm_metrics.erl000066400000000000000000000231061224324231000222200ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_vm_metrics.erl %%% @author joe williams %%% @doc %%% convert erlang system metrics to proplists %%% @end %%%----------------------------------------------------------------- -module(folsom_vm_metrics). -export([get_system_info/0, get_statistics/0, get_memory/0, get_process_info/0, get_port_info/0, get_ets_info/0, get_dets_info/0 ]). %% exported for eunit test -export([convert_system_info/2]). -include("folsom.hrl"). % api get_memory() -> erlang:memory(). get_statistics() -> [{Key, convert_statistics(Key, get_statistics(Key))} || Key <- ?STATISTICS]. get_system_info() -> [{Key, convert_system_info(Key, get_system_info(Key))} || Key <- ?SYSTEM_INFO]. get_process_info() -> [{pid_port_fun_to_atom(Pid), get_process_info(Pid)} || Pid <- processes()]. get_port_info() -> [{pid_port_fun_to_atom(Port), get_port_info(Port)} || Port <- erlang:ports()]. get_ets_info() -> [{Tab, get_ets_dets_info(ets, Tab)} || Tab <- ets:all()]. get_dets_info() -> [{Tab, get_ets_dets_info(dets, Tab)} || Tab <- dets:all()]. % internal functions % wrap system_info and statistics in a try/catch in case keys are missing % in old/new versions of erlang get_system_info(Key) -> try erlang:system_info(Key) catch error:badarg->undefined end. get_statistics(Key) -> try erlang:statistics(Key) catch error:badarg->undefined end. %% conversion functions for erlang:statistics(Key) convert_statistics(context_switches, {ContextSwitches, 0}) -> ContextSwitches; convert_statistics(garbage_collection, {NumberofGCs, WordsReclaimed, 0}) -> [{number_of_gcs, NumberofGCs}, {words_reclaimed, WordsReclaimed}]; convert_statistics(io, {Input, Output}) -> [Input, Output]; convert_statistics(reductions, {TotalReductions, ReductionsSinceLastCall}) -> [{total_reductions, TotalReductions}, {reductions_since_last_call, ReductionsSinceLastCall}]; convert_statistics(runtime, {TotalRunTime, TimeSinceLastCall}) -> [{total_run_time, TotalRunTime}, {time_since_last_call, TimeSinceLastCall}]; convert_statistics(wall_clock, {TotalWallclockTime, WallclockTimeSinceLastCall}) -> [{total_wall_clock_time, TotalWallclockTime}, {wall_clock_time_since_last_call, WallclockTimeSinceLastCall}]; convert_statistics(_, Value) -> Value. %% conversion functions for erlang:system_info(Key) convert_system_info(allocated_areas, List) -> [convert_allocated_areas(Value) || Value <- List]; convert_system_info(allocator, {_,_,_,List}) -> List; convert_system_info(c_compiler_used, {Compiler, Version}) -> [{compiler, Compiler}, {version, convert_c_compiler_version(Version)}]; convert_system_info(cpu_topology, undefined) -> undefined; convert_system_info(cpu_topology, List) when is_list(List) -> [{Type, convert_cpu_topology(Item, [])} || {Type, Item} <- List]; convert_system_info(cpu_topology, {logical,Item}) -> convert_system_info(cpu_topology, [{processor,[{core,{logical,Item}}]}]); convert_system_info(dist_ctrl, List) -> lists:map(fun({Node, Socket}) -> {ok, Stats} = inet:getstat(Socket), {Node, Stats} end, List); convert_system_info(driver_version, Value) -> list_to_binary(Value); convert_system_info(machine, Value) -> list_to_binary(Value); convert_system_info(otp_release, Value) -> list_to_binary(Value); convert_system_info(scheduler_bindings, Value) -> tuple_to_list(Value); convert_system_info(system_version, Value) -> list_to_binary(Value); convert_system_info(system_architecture, Value) -> list_to_binary(Value); convert_system_info(version, Value) -> list_to_binary(Value); convert_system_info(_, Value) -> Value. convert_allocated_areas({Key, Value1, Value2}) -> {Key, [Value1, Value2]}; convert_allocated_areas({Key, Value}) -> {Key, Value}. convert_c_compiler_version({A, B, C}) -> list_to_binary(io_lib:format("~p.~p.~p", [A, B, C])); convert_c_compiler_version({A, B}) -> list_to_binary(io_lib:format("~p.~p", [A, B])); convert_c_compiler_version(A) -> list_to_binary(io_lib:format("~p", [A])). convert_cpu_topology([{core, Value}| Tail], Acc) when is_tuple(Value) -> convert_cpu_topology(Tail, lists:append(Acc, [{core, tuple_to_list(Value)}])); convert_cpu_topology([{core, Value}| Tail], Acc) when is_list(Value) -> convert_cpu_topology(Tail, lists:append(Acc, [{core, convert_cpu_topology(Value, [])}])); convert_cpu_topology([{thread, Value}| Tail], Acc) -> convert_cpu_topology(Tail, lists:append(Acc, [{thread, tuple_to_list(Value)}])); convert_cpu_topology([{node, Value}| Tail], Acc) -> convert_cpu_topology(Tail, lists:append(Acc, [{node, convert_cpu_topology(Value, [])}])); convert_cpu_topology([{processor, Value}| Tail], Acc) -> convert_cpu_topology(Tail, lists:append(Acc, [{processor, convert_cpu_topology(Value, [])}])); convert_cpu_topology({Key, Value}, _) -> [{Key, Value}]; convert_cpu_topology([], Acc) -> Acc. get_process_info(Pid) -> Info = [process_info(Pid, Key) || Key <- ?PROCESS_INFO], lists:flatten([convert_pid_info(Item) || Item <- Info]). get_port_info(Port) -> Stat = get_socket_getstat(Port), SockName = get_socket_sockname(Port), Opts = get_socket_opts(Port), Info = get_erlang_port_info(Port), Protocol = get_socket_protocol(Port), Status = get_socket_status(Port), Type = get_socket_type(Port), lists:flatten(lists:append([ Stat, SockName, Opts, Info, Protocol, Status, Type ])). get_socket_getstat(Socket) -> case catch inet:getstat(Socket) of {ok, Info} -> Info; _ -> [] end. get_socket_status(Socket) -> case catch prim_inet:getstatus(Socket) of {ok, Status} -> [{status, Status}]; _ -> [] end. get_erlang_port_info(Port) -> Info = erlang:port_info(Port), [convert_port_info(Item) || Item <- Info]. get_socket_type(Socket) -> case catch prim_inet:gettype(Socket) of {ok, Type} -> [{type, tuple_to_list(Type)}]; _ -> [] end. get_socket_opts(Socket) -> [get_socket_opts(Socket, Key) || Key <- ?SOCKET_OPTS]. get_socket_opts(Socket, Key) -> case catch inet:getopts(Socket, [Key]) of {ok, Opt} -> Opt; _ -> [] end. get_socket_protocol(Socket) -> case erlang:port_info(Socket, name) of {name, "tcp_inet"} -> [{protocol, tcp}]; {name, "udp_inet"} -> [{protocol, udp}]; {name,"sctp_inet"} -> [{protocol, sctp}]; _ -> [] end. get_socket_sockname(Socket) -> case catch inet:sockname(Socket) of {ok, {Ip, Port}} -> [{ip, ip_to_binary(Ip)}, {port, Port}]; _ -> [] end. get_ets_dets_info(Type, Tab) -> case Type:info(Tab) of undefined -> []; Entries when is_list(Entries) -> [{Key, pid_port_fun_to_atom(Value)} || {Key, Value} <- Entries] end. ip_to_binary(Tuple) -> iolist_to_binary(string:join(lists:map(fun integer_to_list/1, tuple_to_list(Tuple)), ".")). convert_port_info({name, Name}) -> {name, list_to_binary(Name)}; convert_port_info({links, List}) -> {links, [pid_port_fun_to_atom(Item) || Item <- List]}; convert_port_info({connected, Pid}) -> {connected, pid_port_fun_to_atom(Pid)}; convert_port_info(Item) -> Item. convert_pid_info({current_function, MFA}) -> {current_function, tuple_to_list(MFA)}; convert_pid_info({Key, Term}) when is_pid(Term) or is_port(Term) or is_function(Term) -> {Key, pid_port_fun_to_atom(Term)}; convert_pid_info({links, List}) -> {links, [pid_port_fun_to_atom(Item) || Item <- List]}; convert_pid_info({suspending, List}) -> {suspending, [pid_port_fun_to_atom(Item) || Item <- List]}; convert_pid_info({monitors, List}) -> {monitors, [{Key, pid_port_fun_to_atom(Value)} || {Key, Value} <- List]}; convert_pid_info({monitored_by, List}) -> {monitored_by, [pid_port_fun_to_atom(Item) || Item <- List]}; convert_pid_info({binary, List}) -> {binary, [tuple_to_list(Item) || Item <- List]}; convert_pid_info({initial_call, MFA}) -> {inital_call, tuple_to_list(MFA)}; convert_pid_info(Item) -> Item. pid_port_fun_to_atom(Term) when is_pid(Term) -> erlang:list_to_atom(pid_to_list(Term)); pid_port_fun_to_atom(Term) when is_port(Term) -> erlang:list_to_atom(erlang:port_to_list(Term)); pid_port_fun_to_atom(Term) when is_function(Term) -> erlang:list_to_atom(erlang:fun_to_list(Term)); pid_port_fun_to_atom(Term) -> Term. erlang-folsom-0.8.0+dfsg/test/000077500000000000000000000000001224324231000161535ustar00rootroot00000000000000erlang-folsom-0.8.0+dfsg/test/cpu_topo_data000066400000000000000000000122711224324231000207220ustar00rootroot00000000000000[ %% Intel(R) Xeon(R) CPU X5647 @ 2.93GHz (Dual CPU) [{node,[{processor,[{core,[{thread,{logical,1}}, {thread,{logical,9}}]}, {core,[{thread,{logical,3}},{thread,{logical,11}}]}, {core,[{thread,{logical,5}},{thread,{logical,13}}]}, {core,[{thread,{logical,7}},{thread,{logical,15}}]}]}]}, {node,[{processor,[{core,[{thread,{logical,0}}, {thread,{logical,8}}]}, {core,[{thread,{logical,2}},{thread,{logical,10}}]}, {core,[{thread,{logical,4}},{thread,{logical,12}}]}, {core,[{thread,{logical,6}},{thread,{logical,14}}]}]}]}], %% Intel(R) Xeon(R) CPU X5647 @ 2.93GHz [{processor,[{core,[{thread,{logical,0}}, {thread,{logical,4}}]}, {core,[{thread,{logical,1}},{thread,{logical,5}}]}, {core,[{thread,{logical,2}},{thread,{logical,6}}]}, {core,[{thread,{logical,3}},{thread,{logical,7}}]}]}], %% Intel Core 2 Quad Q8300 LGA775 'Yorkfield' 2.5GHz 4MB-cache (1333FSB) Processor [{processor,[{core,{logical,0}}, {core,{logical,1}}, {core,{logical,2}}, {core,{logical,3}}]}], %% Intel® Xeon® Six Core E5-2620 (2.0 GHz, 7.20 GT/s, 15M L3 Cache) [{processor,[{core,[{thread,{logical,0}}, {thread,{logical,6}}]}, {core,[{thread,{logical,1}},{thread,{logical,7}}]}, {core,[{thread,{logical,2}},{thread,{logical,8}}]}, {core,[{thread,{logical,3}},{thread,{logical,9}}]}, {core,[{thread,{logical,4}},{thread,{logical,10}}]}, {core,[{thread,{logical,5}},{thread,{logical,11}}]}]}], %% OSX undefined, %% Intel(R) Xeon(R) CPU W3530 @ 2.80GHz [{processor,[{core,[{thread,{logical,0}}, {thread,{logical,4}}]}, {core,[{thread,{logical,1}},{thread,{logical,5}}]}, {core,[{thread,{logical,2}},{thread,{logical,6}}]}, {core,[{thread,{logical,3}},{thread,{logical,7}}]}]}], %% Intel(R) Atom(TM) CPU N2800 @ 1.86GHz (8GB memory) [{processor,[{core,[{thread,{logical,0}}, {thread,{logical,1}}]}, {core,[{thread,{logical,2}},{thread,{logical,3}}]}]}], %% Intel(R) Core(TM) i7-2675QM CPU @ 2.20GHz [{processor,[{core,[{thread,{logical,0}}, {thread,{logical,4}}]}, {core,[{thread,{logical,1}},{thread,{logical,5}}]}, {core,[{thread,{logical,2}},{thread,{logical,6}}]}, {core,[{thread,{logical,3}},{thread,{logical,7}}]}]}], %% Intel(R) Xeon(R) CPU L3426 @ 1.87GHz [{processor,[{core,[{thread,{logical,0}}, {thread,{logical,4}}]}, {core,[{thread,{logical,1}},{thread,{logical,5}}]}, {core,[{thread,{logical,2}},{thread,{logical,6}}]}, {core,[{thread,{logical,3}},{thread,{logical,7}}]}]}], %% Intel(R) Core(TM) i7 CPU M 640 @ 2.80GHz (in a vmware fusion vm, one cpu enabled) [{processor,{logical,0}}], %% Intel(R) Core(TM) i7 CPU M 640 @ 2.80GHz (in a vmware fusion vm, two cpus enabled) [{processor,{logical,0}},{processor,{logical,1}}], %% Intel(R) Core(TM) i7 CPU M 640 @ 2.80GHz (in a vmware fusion vm, four cpus enabled) [{processor,{logical,0}}, {processor,{logical,1}}, {processor,{logical,2}}, {processor,{logical,3}}], %% Intel(R) Xeon(R) CPU E5620 @ 2.40GHz (EC2 hi1.4xlarge) [{processor,[{thread,{logical,0}}, {thread,{logical,1}}, {thread,{logical,2}}, {thread,{logical,3}}, {thread,{logical,4}}, {thread,{logical,5}}, {thread,{logical,6}}, {thread,{logical,7}}, {thread,{logical,8}}, {thread,{logical,9}}, {thread,{logical,10}}, {thread,{logical,11}}, {thread,{logical,12}}, {thread,{logical,13}}, {thread,{logical,14}}, {thread,{logical,15}}]}], %% Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz (EC2 cc2.8xlarge) [{processor,[{core,[{thread,{logical,0}}, {thread,{logical,16}}]}, {core,[{thread,{logical,1}},{thread,{logical,17}}]}, {core,[{thread,{logical,2}},{thread,{logical,18}}]}, {core,[{thread,{logical,3}},{thread,{logical,19}}]}, {core,[{thread,{logical,4}},{thread,{logical,20}}]}, {core,[{thread,{logical,5}},{thread,{logical,21}}]}, {core,[{thread,{logical,6}},{thread,{logical,22}}]}, {core,[{thread,{logical,7}},{thread,{logical,23}}]}]}, {processor,[{core,[{thread,{logical,8}}, {thread,{logical,24}}]}, {core,[{thread,{logical,9}},{thread,{logical,25}}]}, {core,[{thread,{logical,10}},{thread,{logical,26}}]}, {core,[{thread,{logical,11}},{thread,{logical,27}}]}, {core,[{thread,{logical,12}},{thread,{logical,28}}]}, {core,[{thread,{logical,13}},{thread,{logical,29}}]}, {core,[{thread,{logical,14}},{thread,{logical,30}}]}, {core,[{thread,{logical,15}},{thread,{logical,31}}]}]}], %% Unknown [{processor,{logical,0}},{processor,{logical,1}}], %% single-core ppc32 {logical,0} ]. erlang-folsom-0.8.0+dfsg/test/folsom_erlang_checks.erl000066400000000000000000000427621224324231000230410ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_erlang_checks.erl %%% @author joe williams %%% @doc %%% @end %%%------------------------------------------------------------------ -module(folsom_erlang_checks). -include_lib("eunit/include/eunit.hrl"). -export([ create_metrics/0, populate_metrics/0, tag_metrics/0, check_metrics/0, check_group_metrics/0, delete_metrics/0, vm_metrics/0, counter_metric/2, cpu_topology/0, c_compiler_used/0, create_delete_metrics/0 ]). -define(DATA, [0, 1, 5, 10, 100, 200, 500, 750, 1000, 2000, 5000]). -define(HUGEDATA, lists:seq(1,10000)). -define(DATA1, [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]). -define(DATA2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]). -include("folsom.hrl"). create_metrics() -> ok = folsom_metrics:new_counter(counter), ok = folsom_metrics:new_counter(counter2), ok = folsom_metrics:new_gauge(<<"gauge">>), ok = folsom_metrics:new_histogram(<<"uniform">>, uniform, 5000), ok = folsom_metrics:new_histogram(<<"hugedata">>, uniform, 5000), ok = folsom_metrics:new_histogram(exdec, exdec), ok = folsom_metrics:new_histogram(none, none, 5000), ok = folsom_metrics:new_histogram(nonea, none, 5000), ok = folsom_metrics:new_histogram(noneb, none, 10), ok = folsom_metrics:new_histogram(nonec, none, 5), ok = folsom_metrics:new_histogram(slide_sorted_a, slide_sorted, 10), ok = folsom_metrics:new_histogram(timed, none, 5000), ok = folsom_metrics:new_histogram(timed2, none, 5000), ok = folsom_metrics:new_history(<<"history">>), ok = folsom_metrics:new_meter(meter), ok = folsom_metrics:new_meter_reader(meter_reader), ok = folsom_metrics:new_duration(duration), ok = folsom_metrics:new_spiral(spiral), ?debugFmt("ensuring meter tick is registered with gen_server~n", []), ok = ensure_meter_tick_exists(2), ?debugFmt("ensuring multiple timer registrations dont cause issues", []), ok = folsom_meter_timer_server:register(meter, folsom_metrics_meter), ok = folsom_meter_timer_server:register(meter, folsom_metrics_meter), ok = folsom_meter_timer_server:register(meter, folsom_metrics_meter), ?debugFmt("~p", [folsom_meter_timer_server:dump()]), {state, List} = folsom_meter_timer_server:dump(), 2 = length(List), %% check a server got started for the spiral metric 1 = length(supervisor:which_children(folsom_sample_slide_sup)), 18 = length(folsom_metrics:get_metrics()), ?debugFmt("~n~nmetrics: ~p~n", [folsom_metrics:get_metrics()]). tag_metrics() -> Group = "mygroup", ok = folsom_metrics:tag_metric(counter, Group), ok = folsom_metrics:tag_metric(counter2, Group), ok = folsom_metrics:tag_metric(<<"gauge">>, Group), ok = folsom_metrics:tag_metric(meter, Group), ok = folsom_metrics:tag_metric(spiral, Group), ?debugFmt("~n~ntagged metrics: ~p, ~p, ~p, ~p and ~p in group ~p~n", [counter,counter2,<<"gauge">>,meter,spiral,Group]). populate_metrics() -> ok = folsom_metrics:notify({counter, {inc, 1}}), ok = folsom_metrics:notify({counter, {dec, 1}}), ok = folsom_metrics:notify({counter2, {inc, 10}}), ok = folsom_metrics:notify({counter2, {dec, 7}}), meck:new(folsom_ets), meck:expect(folsom_ets, notify, fun(_Event) -> meck:exception(error, something_wrong_with_ets) end), {'EXIT', {something_wrong_with_ets, _}} = folsom_metrics:safely_notify({unknown_counter, {inc, 1}}), ok = folsom_metrics:safely_histogram_timed_update(unknown_histogram, fun() -> ok end), ok = folsom_metrics:safely_histogram_timed_update(unknown_histogram, fun(ok) -> ok end, [ok]), 3.141592653589793 = folsom_metrics:safely_histogram_timed_update(unknown_histogram, math, pi, []), UnknownHistogramBegin = folsom_metrics:histogram_timed_begin(unknown_histogram), ok = folsom_metrics:safely_histogram_timed_notify(UnknownHistogramBegin), meck:unload(folsom_ets), ok = folsom_metrics:notify({<<"gauge">>, 2}), [ok = folsom_metrics:notify({<<"uniform">>, Value}) || Value <- ?DATA], [ok = folsom_metrics:notify({<<"hugedata">>, Value}) || Value <- ?HUGEDATA], [ok = folsom_metrics:notify({exdec, Value}) || Value <- lists:seq(1, 100000)], [ok = folsom_metrics:notify({none, Value}) || Value <- ?DATA], [ok = folsom_metrics:notify({nonea, Value}) || Value <- ?DATA1], [ok = folsom_metrics:notify({noneb, Value}) || Value <- ?DATA2], [ok = folsom_metrics:notify({nonec, Value}) || Value <- ?DATA2], [ok = folsom_metrics:notify({slide_sorted_a, Value}) || Value <- ?DATA2], ok = folsom_metrics:notify(tagged_metric, 1, meter, [a, b]), ok = folsom_metrics:notify(tagged_metric, 1, meter, [c]), {error, _, unsupported_metric_type} = folsom_metrics:notify(tagged_unknown_metric, 1, unknown_metric, [tag]), 3.141592653589793 = folsom_metrics:histogram_timed_update(timed, math, pi, []), Begin = folsom_metrics:histogram_timed_begin(timed2), folsom_metrics:histogram_timed_notify(Begin), PopulateDuration = fun() -> ok = folsom_metrics:notify_existing_metric(duration, timer_start, duration), timer:sleep(10), ok = folsom_metrics:notify_existing_metric(duration, timer_end, duration) end, [PopulateDuration() || _ <- lists:seq(1, 10)], ok = folsom_metrics:notify({<<"history">>, "string"}), {error, _, nonexistent_metric} = folsom_metrics:notify({historya, "5"}), ok = folsom_metrics:notify(historya, <<"binary">>, history), ?debugFmt("testing meter ...", []), % simulate an interval tick folsom_metrics_meter:tick(meter), [ok,ok,ok,ok,ok] = [ folsom_metrics:notify({meter, Item}) || Item <- [100, 100, 100, 100, 100]], % simulate an interval tick folsom_metrics_meter:tick(meter), ?debugFmt("testing meter reader ...", []), % simulate an interval tick folsom_metrics_meter_reader:tick(meter_reader), [ok,ok,ok,ok,ok] = [ folsom_metrics:notify({meter_reader, Item}) || Item <- [1, 10, 100, 1000, 10000]], % simulate an interval tick folsom_metrics_meter_reader:tick(meter_reader), folsom_metrics:notify_existing_metric(spiral, 100, spiral). check_metrics() -> 0 = folsom_metrics:get_metric_value(counter), 3 = folsom_metrics:get_metric_value(counter2), ok = folsom_metrics:notify_existing_metric(counter2, clear, counter), 0 = folsom_metrics:get_metric_value(counter2), 2 = folsom_metrics:get_metric_value(<<"gauge">>), true = sets:is_subset(sets:from_list([a,b,c]), folsom_metrics:get_tags(tagged_metric)), [11,12,13,14,15,6,7,8,9,10] = folsom_metrics:get_metric_value(noneb), [11,12,13,14,15] = folsom_metrics:get_metric_value(nonec), [6,7,8,9,10,11,12,13,14,15] = folsom_metrics:get_metric_value(slide_sorted_a), Histogram1 = folsom_metrics:get_histogram_statistics(<<"uniform">>), histogram_checks(Histogram1), HugeHistogram = folsom_metrics:get_histogram_statistics(<<"hugedata">>), huge_histogram_checks(HugeHistogram), % just check exdec for non-zero values Exdec = folsom_metrics:get_histogram_statistics(exdec), ?debugFmt("checking exdec sample~n~p~n", [Exdec]), ok = case proplists:get_value(median, Exdec) of Median when Median > 0 -> ok; _ -> error end, Histogram3 = folsom_metrics:get_histogram_statistics(none), histogram_checks(Histogram3), CoValues = folsom_metrics:get_histogram_statistics(none, nonea), histogram_co_checks(CoValues), List = folsom_metrics:get_metric_value(timed), ?debugFmt("timed update value: ~p", [List]), List2 = folsom_metrics:get_metric_value(timed2), ?debugFmt("timed update value begin/end: ~p", [List2]), 1 = length(folsom_metrics:get_metric_value(<<"history">>)), 1 = length(folsom_metrics:get_metric_value(historya)), ?debugFmt("checking meter~n", []), Meter = folsom_metrics:get_metric_value(meter), ?debugFmt("~p", [Meter]), ok = case proplists:get_value(one, Meter) of Value when Value > 1 -> ok; _ -> error end, ok = case proplists:get_value(day, Meter) of Value1 when Value1 > 0.005 -> ok; _ -> error end, ?debugFmt("checking meter reader~n", []), MeterReader = folsom_metrics:get_metric_value(meter_reader), ?debugFmt("~p~n", [MeterReader]), ok = case proplists:get_value(one, MeterReader) of Value2 when Value2 > 1 -> ok; _ -> error end, %% check duration Dur = folsom_metrics:get_metric_value(duration), duration_check(Dur), %% check spiral [{count, 100}, {one, 100}] = folsom_metrics:get_metric_value(spiral). check_group_metrics() -> Group = "mygroup", Metrics = folsom_metrics:get_metrics_value(Group), 5 = length(Metrics), {counter, 0} = lists:keyfind(counter,1,Metrics), {counter2, 0} = lists:keyfind(counter2,1,Metrics), {<<"gauge">>, 2} = lists:keyfind(<<"gauge">>,1,Metrics), {meter, Meter} = lists:keyfind(meter,1,Metrics), ok = case proplists:get_value(one, Meter) of Value when Value > 1 -> ok; _ -> error end, ok = case proplists:get_value(day, Meter) of Value1 when Value1 > 0.005 -> ok; _ -> error end, {spiral, [{count, 100}, {one, 100}]} = lists:keyfind(spiral,1,Metrics), Counters = folsom_metrics:get_metrics_value(Group,counter), {counter, 0} = lists:keyfind(counter,1,Counters), {counter2, 0} = lists:keyfind(counter2,1,Counters), ok = folsom_metrics:untag_metric(counter2, Group), ok = folsom_metrics:untag_metric(<<"gauge">>, Group), ok = folsom_metrics:untag_metric(meter, Group), ok = folsom_metrics:untag_metric(spiral, Group), ?debugFmt("~n~nuntagged metrics: ~p, ~p, ~p and ~p in group ~p~n", [counter2,<<"gauge">>,meter,spiral,Group]), RemainingMetrics = folsom_metrics:get_metrics_value(Group), 1 = length(RemainingMetrics), {counter, 0} = lists:keyfind(counter,1,Metrics). delete_metrics() -> 21 = length(ets:tab2list(?FOLSOM_TABLE)), ok = folsom_metrics:delete_metric(counter), ok = folsom_metrics:delete_metric(counter2), ok = folsom_metrics:delete_metric(<<"gauge">>), ok = folsom_metrics:delete_metric(<<"hugedata">>), ok = folsom_metrics:delete_metric(<<"uniform">>), ok = folsom_metrics:delete_metric(exdec), ok = folsom_metrics:delete_metric(none), ok = folsom_metrics:delete_metric(<<"history">>), ok = folsom_metrics:delete_metric(historya), ok = folsom_metrics:delete_metric(nonea), ok = folsom_metrics:delete_metric(noneb), ok = folsom_metrics:delete_metric(nonec), ok = folsom_metrics:delete_metric(tagged_metric), ok = folsom_metrics:delete_metric(slide_sorted_a), ok = folsom_metrics:delete_metric(timed), ok = folsom_metrics:delete_metric(timed2), ok = folsom_metrics:delete_metric(testcounter), ok = ensure_meter_tick_exists(2), 1 = length(ets:tab2list(?METER_TABLE)), ok = folsom_metrics:delete_metric(meter), 0 = length(ets:tab2list(?METER_TABLE)), 1 = length(ets:tab2list(?METER_READER_TABLE)), ok = folsom_metrics:delete_metric(meter_reader), 0 = length(ets:tab2list(?METER_READER_TABLE)), ok = ensure_meter_tick_exists(0), ok = folsom_metrics:delete_metric(duration), ok = folsom_metrics:delete_metric(spiral), 0 = length(ets:tab2list(?FOLSOM_TABLE)). vm_metrics() -> List1 = folsom_vm_metrics:get_memory(), true = lists:keymember(total, 1, List1), List2 = folsom_vm_metrics:get_statistics(), true = lists:keymember(context_switches, 1, List2), List3 = folsom_vm_metrics:get_system_info(), true = lists:keymember(allocated_areas, 1, List3), [{_, [{backtrace, _}| _]} | _] = folsom_vm_metrics:get_process_info(), [{_, [{name, _}| _]} | _] = folsom_vm_metrics:get_port_info(). counter_metric(Count, Counter) -> ok = folsom_metrics:new_counter(Counter), ?debugFmt("running ~p counter inc/dec rounds~n", [Count]), for(Count, Counter), Result = folsom_metrics:get_metric_value(Counter), ?debugFmt("counter result: ~p~n", [Result]), 0 = Result. ensure_meter_tick_exists(MeterCnt) -> {state, State} = folsom_meter_timer_server:dump(), MeterCnt = length(State), ok. %% internal function histogram_checks(List) -> ?debugFmt("checking histogram statistics", []), ?debugFmt("~p~n", [List]), 0 = proplists:get_value(min, List), 5000 = proplists:get_value(max, List), 869.6363636363636 = proplists:get_value(arithmetic_mean, List), GeoMean = proplists:get_value(geometric_mean, List), ok = case GeoMean - 100.17443147308997 of GeoDiff when GeoDiff < 0.00000001 -> ok; _ -> error end, Value = proplists:get_value(harmonic_mean, List), %?debugFmt("~p~n", [Value]), ok = case Value - 8.333122900936845 of Diff when Diff < 0.00000001 -> ok; _ -> error end, 200 = proplists:get_value(median, List), 2254368.454545454 = proplists:get_value(variance, List), 1501.4554454080394 = proplists:get_value(standard_deviation, List), 1.8399452806806476 = proplists:get_value(skewness, List), 2.2856772911293204 = proplists:get_value(kurtosis, List), List1 = proplists:get_value(percentile, List), percentile_check(List1), List2 = proplists:get_value(histogram, List), histogram_check(List2). huge_histogram_checks(List) -> Skew = erlang:abs(proplists:get_value(skewness, List)), A = skewness_is_too_high_for_sample_of_this_size, B = this_event_is_very_unprobable, C = please_rerun_this_test__if_it_fails_again_your_code_is_bugged, {A, B, C, true} = {A, B, C, Skew < 0.2}. histogram_co_checks(List) -> ?debugFmt("checking histogram covariance and etc statistics", []), ?debugFmt("~p~n", [List]), [ {covariance,17209.545454545456}, {tau,1.0}, {rho,0.760297020598996}, {r,1.0} ] = List. percentile_check(List) -> 750 = proplists:get_value(75, List), 2000 = proplists:get_value(95, List), 5000 = proplists:get_value(99, List), 5000 = proplists:get_value(999, List). histogram_check(List) -> [{2400,10},{5000,1},{8000,0}] = List. counter_inc_dec(Counter) -> ok = folsom_metrics:notify({Counter, {inc, 1}}), ok = folsom_metrics:notify({Counter, {dec, 1}}). for(N, Counter) -> for(N, 0, Counter). for(N, Count, _Counter) when N == Count -> ok; for(N, LoopCount, Counter) -> counter_inc_dec(Counter), for(N, LoopCount + 1, Counter). cpu_topology() -> ?debugFmt("Testing various CPU topologies ...~n", []), {ok, [Data]} = file:consult("../test/cpu_topo_data"), [run_convert_and_jsonify(Item) || Item <- Data]. run_convert_and_jsonify(Item) -> ?debugFmt("Converting ... ~n~p~n", [Item]), Result = folsom_vm_metrics:convert_system_info(cpu_topology, Item), %?debugFmt("~p~n", [mochijson2:encode(Result)]). mochijson2:encode(Result). c_compiler_used() -> Test = [{gnuc, {4,4,5}}, {gnuc, {4,4}}, {msc, 1600}], Expected = [[{compiler, gnuc}, {version, <<"4.4.5">>}], [{compiler, gnuc}, {version, <<"4.4">>}], [{compiler, msc}, {version, <<"1600">>}]], ?assertEqual(Expected, [folsom_vm_metrics:convert_system_info(c_compiler_used, {Compiler, Version}) || {Compiler, Version} <- Test]). duration_check(Duration) -> [?assert(lists:keymember(Key, 1, Duration)) || Key <- [count, last, min, max, arithmetic_mean, geometric_mean, harmonic_mean, median, variance, standard_deviation, skewness, kurtosis, percentile, histogram]], ?assertEqual(10, proplists:get_value(count, Duration)), Last = proplists:get_value(last, Duration), ?assert(Last > 10000). create_delete_metrics() -> ?assertMatch(ok, folsom_metrics:new_counter(counter)), ?assertMatch(ok, folsom_metrics:notify_existing_metric(counter, {inc, 1}, counter)), ?assertMatch(1, folsom_metrics:get_metric_value(counter)), ?assertMatch(ok, folsom_metrics:delete_metric(counter)), ?assertError(badarg, folsom_metrics:notify_existing_metric(counter, {inc, 1}, counter)), ?assertMatch(ok, folsom_metrics:new_counter(counter)), ?assertMatch(ok, folsom_metrics:notify_existing_metric(counter, {inc, 1}, counter)), ?assertMatch(1, folsom_metrics:get_metric_value(counter)). erlang-folsom-0.8.0+dfsg/test/folsom_sample_slide_test.erl000066400000000000000000000077231224324231000237470ustar00rootroot00000000000000%%% %%% Copyright 2012 - Basho Technologies, Inc. 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_sample_slide.erl %%% @author Russell Brown %%% @doc eunit test for folsom_sample_slide.erl %%% @end %%%------------------------------------------------------------------ -module(folsom_sample_slide_test). -include_lib("eunit/include/eunit.hrl"). -include("folsom.hrl"). -define(HISTO, test_slide). -define(WINDOW, 30). -define(RUNTIME, 90). -define(READINGS, 10). slide_test_() -> {setup, fun () -> folsom:start(), meck:new(folsom_utils) end, fun (_) -> meck:unload(folsom_utils), folsom:stop() end, [{"Create sliding window", fun create/0}, {"test sliding window", {timeout, 30, fun exercise/0}} ]}. create() -> ok = folsom_metrics:new_histogram(?HISTO, slide, ?WINDOW), #histogram{sample=Slide} = folsom_metrics_histogram:get_value(?HISTO), ?assert(is_pid(Slide#slide.server)), ?assertEqual(?WINDOW, Slide#slide.window), ?assertEqual(0, ets:info(Slide#slide.reservoir, size)). exercise() -> %% don't want a trim to happen %% unless we call trim %% so kill the trim server process #histogram{sample=Slide} = folsom_metrics_histogram:get_value(?HISTO), ok = folsom_sample_slide_server:stop(Slide#slide.server), Moments = lists:seq(1, ?RUNTIME), %% pump in 90 seconds worth of readings Moment = lists:foldl(fun(_X, Tick) -> Tock = tick(Tick), [folsom_sample_slide:update(Slide, N) || N <- lists:duplicate(?READINGS, Tock)], Tock end, 0, Moments), %% are all readings in the table? check_table(Slide, Moments), %% get values only returns last ?WINDOW seconds ExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) || N <- lists:seq(?RUNTIME - ?WINDOW, ?RUNTIME)])), Values = lists:sort(folsom_sample_slide:get_values(Slide)), ?assertEqual(ExpectedValues, Values), %% trim the table Trimmed = folsom_sample_slide:trim(Slide#slide.reservoir, ?WINDOW), ?assertEqual((?RUNTIME - ?WINDOW - 1) * ?READINGS, Trimmed), check_table(Slide, lists:seq(?RUNTIME - ?WINDOW, ?RUNTIME)), %% increment the clock past the window tick(Moment, ?WINDOW * 2), %% get values should be empty ?assertEqual([], folsom_sample_slide:get_values(Slide)), %% trim, and table should be empty Trimmed2 = folsom_sample_slide:trim(Slide#slide.reservoir, ?WINDOW), ?assertEqual((?RUNTIME * ?READINGS) - ((?RUNTIME - ?WINDOW - 1) * ?READINGS), Trimmed2), check_table(Slide, []), ok. tick(Moment0, IncrBy) -> Moment = Moment0 + IncrBy, meck:expect(folsom_utils, now_epoch, fun() -> Moment end), Moment. tick(Moment) -> tick(Moment, 1). check_table(Slide, Moments) -> Tab = lists:sort(ets:tab2list(Slide#slide.reservoir)), {Ks, Vs} = lists:unzip(Tab), ExpectedVs = lists:sort(lists:flatten([lists:duplicate(10, N) || N <- Moments])), StrippedKeys = lists:usort([X || {X, _} <- Ks]), ?assertEqual(Moments, StrippedKeys), ?assertEqual(ExpectedVs, lists:sort(Vs)). erlang-folsom-0.8.0+dfsg/test/folsom_tests.erl000066400000000000000000000071031224324231000214010ustar00rootroot00000000000000%%% %%% Copyright 2011, Boundary %%% %%% 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. %%% %%%------------------------------------------------------------------- %%% File: folsom_tests.erl %%% @author joe williams %%% @doc %%% @end %%%------------------------------------------------------------------ -module(folsom_tests). -include_lib("eunit/include/eunit.hrl"). run_test_() -> {setup, fun () -> folsom:start() end, fun (_) -> folsom:stop() end, [{"creating metrics", fun folsom_erlang_checks:create_metrics/0}, {"tagging metrics", fun folsom_erlang_checks:tag_metrics/0}, {"populating metrics", {timeout, 30, fun folsom_erlang_checks:populate_metrics/0}}, {"checking metrics", fun folsom_erlang_checks:check_metrics/0}, {"checking counter metric", fun () -> folsom_erlang_checks:counter_metric(10000, testcounter) end}, {"checking group metrics", fun folsom_erlang_checks:check_group_metrics/0}, {"checking erlang vm metrics", fun folsom_erlang_checks:vm_metrics/0}, {"deleting metrics", fun folsom_erlang_checks:delete_metrics/0}, {"cpu topology test", fun folsom_erlang_checks:cpu_topology/0}, {"c compiler test", fun folsom_erlang_checks:c_compiler_used/0}, {"create and delete tests", fun folsom_erlang_checks:create_delete_metrics/0}]}. configure_test_() -> {foreach, fun setup_app/0, fun cleanup_app/1, [{"start with configured metrics", fun() -> ?assertMatch(ok, application:start(folsom)), [counter, slide, <<"gauge">>, <<"uniform">>] = lists:sort(folsom_metrics:get_metrics()) end}]}. setup_app() -> application:unload(folsom), Env = [{counter, counter}, {gauge, <<"gauge">>}, {histogram, [[<<"uniform">>, uniform, 5000], [slide, slide_uniform, {60, 1028}]]}], application:load({application, folsom, [{mod, {folsom, []}}, {env, Env}]}), ok. cleanup_app(ok) -> lists:foreach(fun folsom_metrics:delete_metric/1, [counter, slide, <<"gauge">>, <<"uniform">>]), application:stop(folsom), application:unload(folsom), ok. update_counter_test() -> Tid = ets:new(sometable, [public, set]), Workers = [spawn_monitor(fun() -> timer:sleep(100-N), folsom_utils:update_counter(Tid, hello, N) end) || N <- lists:seq(1, 100)], wait_for_results(Workers), ?assertEqual([{hello, 5050}], ets:lookup(Tid, hello)). wait_for_results([]) -> ok; wait_for_results(Workers) -> receive {'DOWN', _, _, Pid, Reason} -> case lists:keyfind(Pid, 1, Workers) of false -> wait_for_results(Workers); _ -> case Reason of normal -> wait_for_results(lists:keydelete(Pid, 1, Workers)); _ -> erlang:error(Reason) end end end. erlang-folsom-0.8.0+dfsg/test/mochijson2.erl000066400000000000000000000713421224324231000207410ustar00rootroot00000000000000%% @author Bob Ippolito %% @copyright 2007 Mochi Media, Inc. %% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works %% with binaries as strings, arrays as lists (without an {array, _}) %% wrapper and it only knows how to decode UTF-8 (and ASCII). %% %% JSON terms are decoded as follows (javascript -> erlang): %%
    %%
  • {"key": "value"} -> %% {struct, [{<<"key">>, <<"value">>}]}
  • %%
  • ["array", 123, 12.34, true, false, null] -> %% [<<"array">>, 123, 12.34, true, false, null] %%
  • %%
%%
    %%
  • Strings in JSON decode to UTF-8 binaries in Erlang
  • %%
  • Objects decode to {struct, PropList}
  • %%
  • Numbers decode to integer or float
  • %%
  • true, false, null decode to their respective terms.
  • %%
%% The encoder will accept the same format that the decoder will produce, %% but will also allow additional cases for leniency: %%
    %%
  • atoms other than true, false, null will be considered UTF-8 %% strings (even as a proplist key) %%
  • %%
  • {json, IoList} will insert IoList directly into the output %% with no validation %%
  • %%
  • {array, Array} will be encoded as Array %% (legacy mochijson style) %%
  • %%
  • A non-empty raw proplist will be encoded as an object as long %% as the first pair does not have an atom key of json, struct, %% or array %%
  • %%
-module(mochijson2). -author('bob@mochimedia.com'). -export([encoder/1, encode/1]). -export([decoder/1, decode/1]). % This is a macro to placate syntax highlighters.. -define(Q, $\"). -define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset, column=N+S#decoder.column}). -define(INC_COL(S), S#decoder{offset=1+S#decoder.offset, column=1+S#decoder.column}). -define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset, column=1, line=1+S#decoder.line}). -define(INC_CHAR(S, C), case C of $\n -> S#decoder{column=1, line=1+S#decoder.line, offset=1+S#decoder.offset}; _ -> S#decoder{column=1+S#decoder.column, offset=1+S#decoder.offset} end). -define(IS_WHITESPACE(C), (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). %% @type iolist() = [char() | binary() | iolist()] %% @type iodata() = iolist() | binary() %% @type json_string() = atom | binary() %% @type json_number() = integer() | float() %% @type json_array() = [json_term()] %% @type json_object() = {struct, [{json_string(), json_term()}]} %% @type json_iolist() = {json, iolist()} %% @type json_term() = json_string() | json_number() | json_array() | %% json_object() | json_iolist() -record(encoder, {handler=null, utf8=false}). -record(decoder, {object_hook=null, offset=0, line=1, column=1, state=null}). %% @spec encoder([encoder_option()]) -> function() %% @doc Create an encoder/1 with the given options. %% @type encoder_option() = handler_option() | utf8_option() %% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false) encoder(Options) -> State = parse_encoder_options(Options, #encoder{}), fun (O) -> json_encode(O, State) end. %% @spec encode(json_term()) -> iolist() %% @doc Encode the given as JSON to an iolist. encode(Any) -> json_encode(Any, #encoder{}). %% @spec decoder([decoder_option()]) -> function() %% @doc Create a decoder/1 with the given options. decoder(Options) -> State = parse_decoder_options(Options, #decoder{}), fun (O) -> json_decode(O, State) end. %% @spec decode(iolist()) -> json_term() %% @doc Decode the given iolist to Erlang terms. decode(S) -> json_decode(S, #decoder{}). %% Internal API parse_encoder_options([], State) -> State; parse_encoder_options([{handler, Handler} | Rest], State) -> parse_encoder_options(Rest, State#encoder{handler=Handler}); parse_encoder_options([{utf8, Switch} | Rest], State) -> parse_encoder_options(Rest, State#encoder{utf8=Switch}). parse_decoder_options([], State) -> State; parse_decoder_options([{object_hook, Hook} | Rest], State) -> parse_decoder_options(Rest, State#decoder{object_hook=Hook}). json_encode(true, _State) -> <<"true">>; json_encode(false, _State) -> <<"false">>; json_encode(null, _State) -> <<"null">>; json_encode(I, _State) when is_integer(I) -> integer_to_list(I); json_encode(F, _State) when is_float(F) -> mochinum:digits(F); json_encode(S, State) when is_binary(S); is_atom(S) -> json_encode_string(S, State); json_encode([{K, _}|_] = Props, State) when (K =/= struct andalso K =/= array andalso K =/= json) -> json_encode_proplist(Props, State); json_encode({struct, Props}, State) when is_list(Props) -> json_encode_proplist(Props, State); json_encode(Array, State) when is_list(Array) -> json_encode_array(Array, State); json_encode({array, Array}, State) when is_list(Array) -> json_encode_array(Array, State); json_encode({json, IoList}, _State) -> IoList; json_encode(Bad, #encoder{handler=null}) -> exit({json_encode, {bad_term, Bad}}); json_encode(Bad, State=#encoder{handler=Handler}) -> json_encode(Handler(Bad), State). json_encode_array([], _State) -> <<"[]">>; json_encode_array(L, State) -> F = fun (O, Acc) -> [$,, json_encode(O, State) | Acc] end, [$, | Acc1] = lists:foldl(F, "[", L), lists:reverse([$\] | Acc1]). json_encode_proplist([], _State) -> <<"{}">>; json_encode_proplist(Props, State) -> F = fun ({K, V}, Acc) -> KS = json_encode_string(K, State), VS = json_encode(V, State), [$,, VS, $:, KS | Acc] end, [$, | Acc1] = lists:foldl(F, "{", Props), lists:reverse([$\} | Acc1]). json_encode_string(A, State) when is_atom(A) -> L = atom_to_list(A), case json_string_is_safe(L) of true -> [?Q, L, ?Q]; false -> json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q]) end; json_encode_string(B, State) when is_binary(B) -> case json_bin_is_safe(B) of true -> [?Q, B, ?Q]; false -> json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q]) end; json_encode_string(I, _State) when is_integer(I) -> [?Q, integer_to_list(I), ?Q]; json_encode_string(L, State) when is_list(L) -> case json_string_is_safe(L) of true -> [?Q, L, ?Q]; false -> json_encode_string_unicode(L, State, [?Q]) end. json_string_is_safe([]) -> true; json_string_is_safe([C | Rest]) -> case C of ?Q -> false; $\\ -> false; $\b -> false; $\f -> false; $\n -> false; $\r -> false; $\t -> false; C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> false; C when C < 16#7f -> json_string_is_safe(Rest); _ -> false end. json_bin_is_safe(<<>>) -> true; json_bin_is_safe(<>) -> case C of ?Q -> false; $\\ -> false; $\b -> false; $\f -> false; $\n -> false; $\r -> false; $\t -> false; C when C >= 0, C < $\s; C >= 16#7f -> false; C when C < 16#7f -> json_bin_is_safe(Rest) end. json_encode_string_unicode([], _State, Acc) -> lists:reverse([$\" | Acc]); json_encode_string_unicode([C | Cs], State, Acc) -> Acc1 = case C of ?Q -> [?Q, $\\ | Acc]; %% Escaping solidus is only useful when trying to protect %% against "" injection attacks which are only %% possible when JSON is inserted into a HTML document %% in-line. mochijson2 does not protect you from this, so %% if you do insert directly into HTML then you need to %% uncomment the following case or escape the output of encode. %% %% $/ -> %% [$/, $\\ | Acc]; %% $\\ -> [$\\, $\\ | Acc]; $\b -> [$b, $\\ | Acc]; $\f -> [$f, $\\ | Acc]; $\n -> [$n, $\\ | Acc]; $\r -> [$r, $\\ | Acc]; $\t -> [$t, $\\ | Acc]; C when C >= 0, C < $\s -> [unihex(C) | Acc]; C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 -> [xmerl_ucs:to_utf8(C) | Acc]; C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 -> [unihex(C) | Acc]; C when C < 16#7f -> [C | Acc]; _ -> exit({json_encode, {bad_char, C}}) end, json_encode_string_unicode(Cs, State, Acc1). hexdigit(C) when C >= 0, C =< 9 -> C + $0; hexdigit(C) when C =< 15 -> C + $a - 10. unihex(C) when C < 16#10000 -> <> = <>, Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]], [$\\, $u | Digits]; unihex(C) when C =< 16#10FFFF -> N = C - 16#10000, S1 = 16#d800 bor ((N bsr 10) band 16#3ff), S2 = 16#dc00 bor (N band 16#3ff), [unihex(S1), unihex(S2)]. json_decode(L, S) when is_list(L) -> json_decode(iolist_to_binary(L), S); json_decode(B, S) -> {Res, S1} = decode1(B, S), {eof, _} = tokenize(B, S1#decoder{state=trim}), Res. decode1(B, S=#decoder{state=null}) -> case tokenize(B, S#decoder{state=any}) of {{const, C}, S1} -> {C, S1}; {start_array, S1} -> decode_array(B, S1); {start_object, S1} -> decode_object(B, S1) end. make_object(V, #decoder{object_hook=null}) -> V; make_object(V, #decoder{object_hook=Hook}) -> Hook(V). decode_object(B, S) -> decode_object(B, S#decoder{state=key}, []). decode_object(B, S=#decoder{state=key}, Acc) -> case tokenize(B, S) of {end_object, S1} -> V = make_object({struct, lists:reverse(Acc)}, S1), {V, S1#decoder{state=null}}; {{const, K}, S1} -> {colon, S2} = tokenize(B, S1), {V, S3} = decode1(B, S2#decoder{state=null}), decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc]) end; decode_object(B, S=#decoder{state=comma}, Acc) -> case tokenize(B, S) of {end_object, S1} -> V = make_object({struct, lists:reverse(Acc)}, S1), {V, S1#decoder{state=null}}; {comma, S1} -> decode_object(B, S1#decoder{state=key}, Acc) end. decode_array(B, S) -> decode_array(B, S#decoder{state=any}, []). decode_array(B, S=#decoder{state=any}, Acc) -> case tokenize(B, S) of {end_array, S1} -> {lists:reverse(Acc), S1#decoder{state=null}}; {start_array, S1} -> {Array, S2} = decode_array(B, S1), decode_array(B, S2#decoder{state=comma}, [Array | Acc]); {start_object, S1} -> {Array, S2} = decode_object(B, S1), decode_array(B, S2#decoder{state=comma}, [Array | Acc]); {{const, Const}, S1} -> decode_array(B, S1#decoder{state=comma}, [Const | Acc]) end; decode_array(B, S=#decoder{state=comma}, Acc) -> case tokenize(B, S) of {end_array, S1} -> {lists:reverse(Acc), S1#decoder{state=null}}; {comma, S1} -> decode_array(B, S1#decoder{state=any}, Acc) end. tokenize_string(B, S=#decoder{offset=O}) -> case tokenize_string_fast(B, O) of {escape, O1} -> Length = O1 - O, S1 = ?ADV_COL(S, Length), <<_:O/binary, Head:Length/binary, _/binary>> = B, tokenize_string(B, S1, lists:reverse(binary_to_list(Head))); O1 -> Length = O1 - O, <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B, {{const, String}, ?ADV_COL(S, Length + 1)} end. tokenize_string_fast(B, O) -> case B of <<_:O/binary, ?Q, _/binary>> -> O; <<_:O/binary, $\\, _/binary>> -> {escape, O}; <<_:O/binary, C1, _/binary>> when C1 < 128 -> tokenize_string_fast(B, 1 + O); <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, C2 >= 128, C2 =< 191 -> tokenize_string_fast(B, 2 + O); <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, C2 >= 128, C2 =< 191, C3 >= 128, C3 =< 191 -> tokenize_string_fast(B, 3 + O); <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, C2 >= 128, C2 =< 191, C3 >= 128, C3 =< 191, C4 >= 128, C4 =< 191 -> tokenize_string_fast(B, 4 + O); _ -> throw(invalid_utf8) end. tokenize_string(B, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, ?Q, _/binary>> -> {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)}; <<_:O/binary, "\\\"", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]); <<_:O/binary, "\\\\", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]); <<_:O/binary, "\\/", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]); <<_:O/binary, "\\b", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]); <<_:O/binary, "\\f", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]); <<_:O/binary, "\\n", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]); <<_:O/binary, "\\r", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]); <<_:O/binary, "\\t", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]); <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> -> C = erlang:list_to_integer([C3, C2, C1, C0], 16), if C > 16#D7FF, C < 16#DC00 -> %% coalesce UTF-16 surrogate pair <<"\\u", D3, D2, D1, D0, _/binary>> = Rest, D = erlang:list_to_integer([D3,D2,D1,D0], 16), [CodePoint] = xmerl_ucs:from_utf16be(<>), Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc), tokenize_string(B, ?ADV_COL(S, 12), Acc1); true -> Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc), tokenize_string(B, ?ADV_COL(S, 6), Acc1) end; <<_:O/binary, C1, _/binary>> when C1 < 128 -> tokenize_string(B, ?INC_CHAR(S, C1), [C1 | Acc]); <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, C2 >= 128, C2 =< 191 -> tokenize_string(B, ?ADV_COL(S, 2), [C2, C1 | Acc]); <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, C2 >= 128, C2 =< 191, C3 >= 128, C3 =< 191 -> tokenize_string(B, ?ADV_COL(S, 3), [C3, C2, C1 | Acc]); <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, C2 >= 128, C2 =< 191, C3 >= 128, C3 =< 191, C4 >= 128, C4 =< 191 -> tokenize_string(B, ?ADV_COL(S, 4), [C4, C3, C2, C1 | Acc]); _ -> throw(invalid_utf8) end. tokenize_number(B, S) -> case tokenize_number(B, sign, S, []) of {{int, Int}, S1} -> {{const, list_to_integer(Int)}, S1}; {{float, Float}, S1} -> {{const, list_to_float(Float)}, S1} end. tokenize_number(B, sign, S=#decoder{offset=O}, []) -> case B of <<_:O/binary, $-, _/binary>> -> tokenize_number(B, int, ?INC_COL(S), [$-]); _ -> tokenize_number(B, int, S, []) end; tokenize_number(B, int, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, $0, _/binary>> -> tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]); <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 -> tokenize_number(B, int1, ?INC_COL(S), [C | Acc]) end; tokenize_number(B, int1, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> tokenize_number(B, int1, ?INC_COL(S), [C | Acc]); _ -> tokenize_number(B, frac, S, Acc) end; tokenize_number(B, frac, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 -> tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]); <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]); _ -> {{int, lists:reverse(Acc)}, S} end; tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]); <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]); _ -> {{float, lists:reverse(Acc)}, S} end; tokenize_number(B, esign, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ -> tokenize_number(B, eint, ?INC_COL(S), [C | Acc]); _ -> tokenize_number(B, eint, S, Acc) end; tokenize_number(B, eint, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]) end; tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]); _ -> {{float, lists:reverse(Acc)}, S} end. tokenize(B, S=#decoder{offset=O}) -> case B of <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> tokenize(B, ?INC_CHAR(S, C)); <<_:O/binary, "{", _/binary>> -> {start_object, ?INC_COL(S)}; <<_:O/binary, "}", _/binary>> -> {end_object, ?INC_COL(S)}; <<_:O/binary, "[", _/binary>> -> {start_array, ?INC_COL(S)}; <<_:O/binary, "]", _/binary>> -> {end_array, ?INC_COL(S)}; <<_:O/binary, ",", _/binary>> -> {comma, ?INC_COL(S)}; <<_:O/binary, ":", _/binary>> -> {colon, ?INC_COL(S)}; <<_:O/binary, "null", _/binary>> -> {{const, null}, ?ADV_COL(S, 4)}; <<_:O/binary, "true", _/binary>> -> {{const, true}, ?ADV_COL(S, 4)}; <<_:O/binary, "false", _/binary>> -> {{const, false}, ?ADV_COL(S, 5)}; <<_:O/binary, "\"", _/binary>> -> tokenize_string(B, ?INC_COL(S)); <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9) orelse C =:= $- -> tokenize_number(B, S); <<_:O/binary>> -> trim = S#decoder.state, {eof, S} end. %% %% Tests %% -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). %% testing constructs borrowed from the Yaws JSON implementation. %% Create an object from a list of Key/Value pairs. obj_new() -> {struct, []}. is_obj({struct, Props}) -> F = fun ({K, _}) when is_binary(K) -> true end, lists:all(F, Props). obj_from_list(Props) -> Obj = {struct, Props}, ?assert(is_obj(Obj)), Obj. %% Test for equivalence of Erlang terms. %% Due to arbitrary order of construction, equivalent objects might %% compare unequal as erlang terms, so we need to carefully recurse %% through aggregates (tuples and objects). equiv({struct, Props1}, {struct, Props2}) -> equiv_object(Props1, Props2); equiv(L1, L2) when is_list(L1), is_list(L2) -> equiv_list(L1, L2); equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2; equiv(A, A) when A =:= true orelse A =:= false orelse A =:= null -> true. %% Object representation and traversal order is unknown. %% Use the sledgehammer and sort property lists. equiv_object(Props1, Props2) -> L1 = lists:keysort(1, Props1), L2 = lists:keysort(1, Props2), Pairs = lists:zip(L1, L2), true = lists:all(fun({{K1, V1}, {K2, V2}}) -> equiv(K1, K2) and equiv(V1, V2) end, Pairs). %% Recursively compare tuple elements for equivalence. equiv_list([], []) -> true; equiv_list([V1 | L1], [V2 | L2]) -> equiv(V1, V2) andalso equiv_list(L1, L2). decode_test() -> [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>), <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]). e2j_vec_test() -> test_one(e2j_test_vec(utf8), 1). test_one([], _N) -> %% io:format("~p tests passed~n", [N-1]), ok; test_one([{E, J} | Rest], N) -> %% io:format("[~p] ~p ~p~n", [N, E, J]), true = equiv(E, decode(J)), true = equiv(E, decode(encode(E))), test_one(Rest, 1+N). e2j_test_vec(utf8) -> [ {1, "1"}, {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes {-1, "-1"}, {-3.1416, "-3.14160"}, {12.0e10, "1.20000e+11"}, {1.234E+10, "1.23400e+10"}, {-1.234E-10, "-1.23400e-10"}, {10.0, "1.0e+01"}, {123.456, "1.23456E+2"}, {10.0, "1e1"}, {<<"foo">>, "\"foo\""}, {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""}, {<<"">>, "\"\""}, {<<"\n\n\n">>, "\"\\n\\n\\n\""}, {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""}, {obj_new(), "{}"}, {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"}, {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]), "{\"foo\":\"bar\",\"baz\":123}"}, {[], "[]"}, {[[]], "[[]]"}, {[1, <<"foo">>], "[1,\"foo\"]"}, %% json array in a json object {obj_from_list([{<<"foo">>, [123]}]), "{\"foo\":[123]}"}, %% json object in a json object {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]), "{\"foo\":{\"bar\":true}}"}, %% fold evaluation order {obj_from_list([{<<"foo">>, []}, {<<"bar">>, obj_from_list([{<<"baz">>, true}])}, {<<"alice">>, <<"bob">>}]), "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"}, %% json object in a json array {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null], "[-123,\"foo\",{\"bar\":[]},null]"} ]. %% test utf8 encoding encoder_utf8_test() -> %% safe conversion case (default) [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] = encode(<<1,"\321\202\320\265\321\201\321\202">>), %% raw utf8 output (optional) Enc = mochijson2:encoder([{utf8, true}]), [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] = Enc(<<1,"\321\202\320\265\321\201\321\202">>). input_validation_test() -> Good = [ {16#00A3, <>}, %% pound {16#20AC, <>}, %% euro {16#10196, <>} %% denarius ], lists:foreach(fun({CodePoint, UTF8}) -> Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)), Expect = decode(UTF8) end, Good), Bad = [ %% 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte <>, %% missing continuations, last byte in each should be 80-BF <>, <>, <>, %% we don't support code points > 10FFFF per RFC 3629 <>, %% escape characters trigger a different code path <> ], lists:foreach( fun(X) -> ok = try decode(X) catch invalid_utf8 -> ok end, %% could be {ucs,{bad_utf8_character_code}} or %% {json_encode,{bad_char,_}} {'EXIT', _} = (catch encode(X)) end, Bad). inline_json_test() -> ?assertEqual(<<"\"iodata iodata\"">>, iolist_to_binary( encode({json, [<<"\"iodata">>, " iodata\""]}))), ?assertEqual({struct, [{<<"key">>, <<"iodata iodata">>}]}, decode( encode({struct, [{key, {json, [<<"\"iodata">>, " iodata\""]}}]}))), ok. big_unicode_test() -> UTF8Seq = list_to_binary(xmerl_ucs:to_utf8(16#0001d120)), ?assertEqual( <<"\"\\ud834\\udd20\"">>, iolist_to_binary(encode(UTF8Seq))), ?assertEqual( UTF8Seq, decode(iolist_to_binary(encode(UTF8Seq)))), ok. custom_decoder_test() -> ?assertEqual( {struct, [{<<"key">>, <<"value">>}]}, (decoder([]))("{\"key\": \"value\"}")), F = fun ({struct, [{<<"key">>, <<"value">>}]}) -> win end, ?assertEqual( win, (decoder([{object_hook, F}]))("{\"key\": \"value\"}")), ok. atom_test() -> %% JSON native atoms [begin ?assertEqual(A, decode(atom_to_list(A))), ?assertEqual(iolist_to_binary(atom_to_list(A)), iolist_to_binary(encode(A))) end || A <- [true, false, null]], %% Atom to string ?assertEqual( <<"\"foo\"">>, iolist_to_binary(encode(foo))), ?assertEqual( <<"\"\\ud834\\udd20\"">>, iolist_to_binary(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))), ok. key_encode_test() -> %% Some forms are accepted as keys that would not be strings in other %% cases ?assertEqual( <<"{\"foo\":1}">>, iolist_to_binary(encode({struct, [{foo, 1}]}))), ?assertEqual( <<"{\"foo\":1}">>, iolist_to_binary(encode({struct, [{<<"foo">>, 1}]}))), ?assertEqual( <<"{\"foo\":1}">>, iolist_to_binary(encode({struct, [{"foo", 1}]}))), ?assertEqual( <<"{\"foo\":1}">>, iolist_to_binary(encode([{foo, 1}]))), ?assertEqual( <<"{\"foo\":1}">>, iolist_to_binary(encode([{<<"foo">>, 1}]))), ?assertEqual( <<"{\"foo\":1}">>, iolist_to_binary(encode([{"foo", 1}]))), ?assertEqual( <<"{\"\\ud834\\udd20\":1}">>, iolist_to_binary( encode({struct, [{[16#0001d120], 1}]}))), ?assertEqual( <<"{\"1\":1}">>, iolist_to_binary(encode({struct, [{1, 1}]}))), ok. unsafe_chars_test() -> Chars = "\"\\\b\f\n\r\t", [begin ?assertEqual(false, json_string_is_safe([C])), ?assertEqual(false, json_bin_is_safe(<>)), ?assertEqual(<>, decode(encode(<>))) end || C <- Chars], ?assertEqual( false, json_string_is_safe([16#0001d120])), ?assertEqual( false, json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8(16#0001d120)))), ?assertEqual( [16#0001d120], xmerl_ucs:from_utf8( binary_to_list( decode(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))))), ?assertEqual( false, json_string_is_safe([16#110000])), ?assertEqual( false, json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8([16#110000])))), %% solidus can be escaped but isn't unsafe by default ?assertEqual( <<"/">>, decode(<<"\"\\/\"">>)), ok. int_test() -> ?assertEqual(0, decode("0")), ?assertEqual(1, decode("1")), ?assertEqual(11, decode("11")), ok. large_int_test() -> ?assertEqual(<<"-2147483649214748364921474836492147483649">>, iolist_to_binary(encode(-2147483649214748364921474836492147483649))), ?assertEqual(<<"2147483649214748364921474836492147483649">>, iolist_to_binary(encode(2147483649214748364921474836492147483649))), ok. float_test() -> ?assertEqual(<<"-2147483649.0">>, iolist_to_binary(encode(-2147483649.0))), ?assertEqual(<<"2147483648.0">>, iolist_to_binary(encode(2147483648.0))), ok. handler_test() -> ?assertEqual( {'EXIT',{json_encode,{bad_term,{}}}}, catch encode({})), F = fun ({}) -> [] end, ?assertEqual( <<"[]">>, iolist_to_binary((encoder([{handler, F}]))({}))), ok. -endif. erlang-folsom-0.8.0+dfsg/test/mochinum.erl000066400000000000000000000244131224324231000205020ustar00rootroot00000000000000%% @copyright 2007 Mochi Media, Inc. %% @author Bob Ippolito %% @doc Useful numeric algorithms for floats that cover some deficiencies %% in the math module. More interesting is digits/1, which implements %% the algorithm from: %% http://www.cs.indiana.edu/~burger/fp/index.html %% See also "Printing Floating-Point Numbers Quickly and Accurately" %% in Proceedings of the SIGPLAN '96 Conference on Programming Language %% Design and Implementation. -module(mochinum). -author("Bob Ippolito "). -export([digits/1, frexp/1, int_pow/2, int_ceil/1]). %% IEEE 754 Float exponent bias -define(FLOAT_BIAS, 1022). -define(MIN_EXP, -1074). -define(BIG_POW, 4503599627370496). %% External API %% @spec digits(number()) -> string() %% @doc Returns a string that accurately represents the given integer or float %% using a conservative amount of digits. Great for generating %% human-readable output, or compact ASCII serializations for floats. digits(N) when is_integer(N) -> integer_to_list(N); digits(0.0) -> "0.0"; digits(Float) -> {Frac1, Exp1} = frexp_int(Float), [Place0 | Digits0] = digits1(Float, Exp1, Frac1), {Place, Digits} = transform_digits(Place0, Digits0), R = insert_decimal(Place, Digits), case Float < 0 of true -> [$- | R]; _ -> R end. %% @spec frexp(F::float()) -> {Frac::float(), Exp::float()} %% @doc Return the fractional and exponent part of an IEEE 754 double, %% equivalent to the libc function of the same name. %% F = Frac * pow(2, Exp). frexp(F) -> frexp1(unpack(F)). %% @spec int_pow(X::integer(), N::integer()) -> Y::integer() %% @doc Moderately efficient way to exponentiate integers. %% int_pow(10, 2) = 100. int_pow(_X, 0) -> 1; int_pow(X, N) when N > 0 -> int_pow(X, N, 1). %% @spec int_ceil(F::float()) -> integer() %% @doc Return the ceiling of F as an integer. The ceiling is defined as %% F when F == trunc(F); %% trunc(F) when F < 0; %% trunc(F) + 1 when F > 0. int_ceil(X) -> T = trunc(X), case (X - T) of Pos when Pos > 0 -> T + 1; _ -> T end. %% Internal API int_pow(X, N, R) when N < 2 -> R * X; int_pow(X, N, R) -> int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end). insert_decimal(0, S) -> "0." ++ S; insert_decimal(Place, S) when Place > 0 -> L = length(S), case Place - L of 0 -> S ++ ".0"; N when N < 0 -> {S0, S1} = lists:split(L + N, S), S0 ++ "." ++ S1; N when N < 6 -> %% More places than digits S ++ lists:duplicate(N, $0) ++ ".0"; _ -> insert_decimal_exp(Place, S) end; insert_decimal(Place, S) when Place > -6 -> "0." ++ lists:duplicate(abs(Place), $0) ++ S; insert_decimal(Place, S) -> insert_decimal_exp(Place, S). insert_decimal_exp(Place, S) -> [C | S0] = S, S1 = case S0 of [] -> "0"; _ -> S0 end, Exp = case Place < 0 of true -> "e-"; false -> "e+" end, [C] ++ "." ++ S1 ++ Exp ++ integer_to_list(abs(Place - 1)). digits1(Float, Exp, Frac) -> Round = ((Frac band 1) =:= 0), case Exp >= 0 of true -> BExp = 1 bsl Exp, case (Frac =/= ?BIG_POW) of true -> scale((Frac * BExp * 2), 2, BExp, BExp, Round, Round, Float); false -> scale((Frac * BExp * 4), 4, (BExp * 2), BExp, Round, Round, Float) end; false -> case (Exp =:= ?MIN_EXP) orelse (Frac =/= ?BIG_POW) of true -> scale((Frac * 2), 1 bsl (1 - Exp), 1, 1, Round, Round, Float); false -> scale((Frac * 4), 1 bsl (2 - Exp), 2, 1, Round, Round, Float) end end. scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) -> Est = int_ceil(math:log10(abs(Float)) - 1.0e-10), %% Note that the scheme implementation uses a 326 element look-up table %% for int_pow(10, N) where we do not. case Est >= 0 of true -> fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est, LowOk, HighOk); false -> Scale = int_pow(10, -Est), fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est, LowOk, HighOk) end. fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) -> TooLow = case HighOk of true -> (R + MPlus) >= S; false -> (R + MPlus) > S end, case TooLow of true -> [(K + 1) | generate(R, S, MPlus, MMinus, LowOk, HighOk)]; false -> [K | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)] end. generate(R0, S, MPlus, MMinus, LowOk, HighOk) -> D = R0 div S, R = R0 rem S, TC1 = case LowOk of true -> R =< MMinus; false -> R < MMinus end, TC2 = case HighOk of true -> (R + MPlus) >= S; false -> (R + MPlus) > S end, case TC1 of false -> case TC2 of false -> [D | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)]; true -> [D + 1] end; true -> case TC2 of false -> [D]; true -> case R * 2 < S of true -> [D]; false -> [D + 1] end end end. unpack(Float) -> <> = <>, {Sign, Exp, Frac}. frexp1({_Sign, 0, 0}) -> {0.0, 0}; frexp1({Sign, 0, Frac}) -> Exp = log2floor(Frac), <> = <>, {Frac1, -(?FLOAT_BIAS) - 52 + Exp}; frexp1({Sign, Exp, Frac}) -> <> = <>, {Frac1, Exp - ?FLOAT_BIAS}. log2floor(Int) -> log2floor(Int, 0). log2floor(0, N) -> N; log2floor(Int, N) -> log2floor(Int bsr 1, 1 + N). transform_digits(Place, [0 | Rest]) -> transform_digits(Place, Rest); transform_digits(Place, Digits) -> {Place, [$0 + D || D <- Digits]}. frexp_int(F) -> case unpack(F) of {_Sign, 0, Frac} -> {Frac, ?MIN_EXP}; {_Sign, Exp, Frac} -> {Frac + (1 bsl 52), Exp - 53 - ?FLOAT_BIAS} end. %% %% Tests %% -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). int_ceil_test() -> ?assertEqual(1, int_ceil(0.0001)), ?assertEqual(0, int_ceil(0.0)), ?assertEqual(1, int_ceil(0.99)), ?assertEqual(1, int_ceil(1.0)), ?assertEqual(-1, int_ceil(-1.5)), ?assertEqual(-2, int_ceil(-2.0)), ok. int_pow_test() -> ?assertEqual(1, int_pow(1, 1)), ?assertEqual(1, int_pow(1, 0)), ?assertEqual(1, int_pow(10, 0)), ?assertEqual(10, int_pow(10, 1)), ?assertEqual(100, int_pow(10, 2)), ?assertEqual(1000, int_pow(10, 3)), ok. digits_test() -> ?assertEqual("0", digits(0)), ?assertEqual("0.0", digits(0.0)), ?assertEqual("1.0", digits(1.0)), ?assertEqual("-1.0", digits(-1.0)), ?assertEqual("0.1", digits(0.1)), ?assertEqual("0.01", digits(0.01)), ?assertEqual("0.001", digits(0.001)), ?assertEqual("1.0e+6", digits(1000000.0)), ?assertEqual("0.5", digits(0.5)), ?assertEqual("4503599627370496.0", digits(4503599627370496.0)), %% small denormalized number %% 4.94065645841246544177e-324 =:= 5.0e-324 <> = <<0,0,0,0,0,0,0,1>>, ?assertEqual("5.0e-324", digits(SmallDenorm)), ?assertEqual(SmallDenorm, list_to_float(digits(SmallDenorm))), %% large denormalized number %% 2.22507385850720088902e-308 <> = <<0,15,255,255,255,255,255,255>>, ?assertEqual("2.225073858507201e-308", digits(BigDenorm)), ?assertEqual(BigDenorm, list_to_float(digits(BigDenorm))), %% small normalized number %% 2.22507385850720138309e-308 <> = <<0,16,0,0,0,0,0,0>>, ?assertEqual("2.2250738585072014e-308", digits(SmallNorm)), ?assertEqual(SmallNorm, list_to_float(digits(SmallNorm))), %% large normalized number %% 1.79769313486231570815e+308 <> = <<127,239,255,255,255,255,255,255>>, ?assertEqual("1.7976931348623157e+308", digits(LargeNorm)), ?assertEqual(LargeNorm, list_to_float(digits(LargeNorm))), %% issue #10 - mochinum:frexp(math:pow(2, -1074)). ?assertEqual("5.0e-324", digits(math:pow(2, -1074))), ok. frexp_test() -> %% zero ?assertEqual({0.0, 0}, frexp(0.0)), %% one ?assertEqual({0.5, 1}, frexp(1.0)), %% negative one ?assertEqual({-0.5, 1}, frexp(-1.0)), %% small denormalized number %% 4.94065645841246544177e-324 <> = <<0,0,0,0,0,0,0,1>>, ?assertEqual({0.5, -1073}, frexp(SmallDenorm)), %% large denormalized number %% 2.22507385850720088902e-308 <> = <<0,15,255,255,255,255,255,255>>, ?assertEqual( {0.99999999999999978, -1022}, frexp(BigDenorm)), %% small normalized number %% 2.22507385850720138309e-308 <> = <<0,16,0,0,0,0,0,0>>, ?assertEqual({0.5, -1021}, frexp(SmallNorm)), %% large normalized number %% 1.79769313486231570815e+308 <> = <<127,239,255,255,255,255,255,255>>, ?assertEqual( {0.99999999999999989, 1024}, frexp(LargeNorm)), %% issue #10 - mochinum:frexp(math:pow(2, -1074)). ?assertEqual( {0.5, -1073}, frexp(math:pow(2, -1074))), ok. -endif. erlang-folsom-0.8.0+dfsg/test/slide_statem_eqc.erl000066400000000000000000000140071224324231000221660ustar00rootroot00000000000000%%% %%% Copyright 2012 - Basho Technologies, Inc. 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. %%% %%%------------------------------------------------------------------- %%% File: slide_statem_eqc.erl %%% @author Russell Brown %%% @doc quickcheck test for the folsom_sample_slide.erl %%% @end %%%------------------------------------------------------------------ -module(slide_statem_eqc). -compile(export_all). -ifdef(TEST). -ifdef(EQC). -include("folsom.hrl"). -include_lib("eqc/include/eqc.hrl"). -include_lib("eqc/include/eqc_statem.hrl"). -include_lib("eunit/include/eunit.hrl"). -define(QC_OUT(P), eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)). -define(WINDOW, 60). -record(state, {moment=1000, sample, name, values=[]}). initial_state() -> meck:expect(folsom_utils, now_epoch, fun() -> 1000 end), #state{}. command(S) -> oneof( [{call, ?MODULE, new_histo, []} || S#state.sample == undefined] ++ [{call, ?MODULE, tick, [S#state.moment]} || S#state.sample /= undefined] ++ [{call, ?MODULE, update, [S#state.sample, int()]} || S#state.sample /= undefined] ++ [{call, ?MODULE, trim, [S#state.sample, ?WINDOW]} || S#state.sample /= undefined] ++ [{call, ?MODULE, get_values, [S#state.sample]} || S#state.sample /= undefined] ). %% Next state transformation, S is the current state next_state(S, V, {call, ?MODULE, new_histo, []}) -> S#state{name={call, erlang, element, [1, V]}, sample={call, erlang, element, [2, V]}}; next_state(S, V, {call, ?MODULE, tick, [_Moment]}) -> S#state{moment=V}; next_state(#state{moment=Moment, values=Values0}=S, V, {call, ?MODULE, update, [_, _Val]}) -> S#state{values=Values0 ++ [{Moment, V}]}; next_state(#state{values=Values, moment=Moment}=S, _V, {call, ?MODULE, trim, _}) -> %% trim the model S#state{values = trim(Values, Moment, ?WINDOW)}; next_state(S,_V,{call, ?MODULE, _, _}) -> S. %% Precondition, checked before command is added to the command sequence precondition(S, {call, _, new_histo, _}) -> S#state.sample == undefined; precondition(S, _) when S#state.sample == undefined -> false; precondition(_S, {call, _, _, _}) -> true. %% Postcondition, checked after command has been evaluated %% OBS: S is the state before next_state(S,_,) postcondition(#state{values=Values0, moment=Moment}, {call, ?MODULE, get_values, _}, Res) -> Values = [V || {K, V} <- Values0, K >= Moment - ?WINDOW], case lists:sort(Values) == lists:sort(Res) of true -> true; _ -> {"get values", {"model", lists:sort(Values)}, {"smaple", lists:sort(Res)}} end; postcondition(#state{values=Values, sample=Sample, moment=Moment}, {call, ?MODULE, trim, _}, _TrimCnt) -> %% check that values and the actual table contents are the same after a trim Table0 = ets:tab2list(Sample#slide.reservoir), Table = [{X, Y} || {{X, _}, Y} <- Table0], Model = lists:sort(trim(Values, Moment, ?WINDOW)), case Model == lists:sort(Table) of true -> true; _ -> {"after trim", {"model", Model}, {"sample", lists:sort(Table)}} end; postcondition(_S, {call, ?MODULE, _, _}, _Res) -> true. prop_window_test_() -> Seconds = 10, {setup, fun() -> ok end, fun(_X) -> (catch meck:unload(folsom_utils)), folsom:stop() end, [{"QuickCheck Test", {timeout, Seconds*2, fun() -> true = eqc:quickcheck(eqc:testing_time(Seconds, ?QC_OUT(prop_window()))) end }}]}. prop_window() -> folsom:start(), (catch meck:new(folsom_utils)), ?FORALL(Cmds, commands(?MODULE), aggregate(command_names(Cmds), begin {H, S, Res} = run_commands(?MODULE, Cmds), {Actual, Expected} = case S#state.sample of undefined -> {S#state.values, []}; Sample -> A = folsom_metrics:get_metric_value(S#state.name), E = [V || {K, V} <- S#state.values, K >= S#state.moment - ?WINDOW], folsom_metrics:delete_metric(S#state.name), {A, E} end, ?WHENFAIL( io:format("History: ~p~nState: ~p~nActual: ~p~nExpected: ~p~nRes: ~p~n", [H, S, Actual, Expected, Res]), conjunction([{total, equals(lists:sort(Actual), lists:sort(Expected))}, {eq, equals(Res, ok)}])) end)). %% Commands new_histo() -> Ref = make_ref(), folsom_metrics:new_histogram(Ref, slide, ?WINDOW), #histogram{sample=Slide} = folsom_metrics_histogram:get_value(Ref), ok = folsom_sample_slide_server:stop(Slide#slide.server), {Ref, Slide}. tick(Moment) -> IncrBy = trunc(random:uniform(10)), meck:expect(folsom_utils, now_epoch, fun() -> Moment + IncrBy end), Moment+IncrBy. update(Sample, Val) -> Sample = folsom_sample_slide:update(Sample, Val), Val. trim(Sample, Window) -> folsom_sample_slide:trim(Sample#slide.reservoir, Window). get_values(Sample) -> folsom_sample_slide:get_values(Sample). %% private trim(L, Moment, Window) -> [{K, V} || {K, V} <- L, K >= Moment - Window]. -endif. -endif. erlang-folsom-0.8.0+dfsg/test/slide_uniform_eqc.erl000066400000000000000000000173271224324231000223600ustar00rootroot00000000000000%%% %%% Copyright 2012 - Basho Technologies, Inc. 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. %%% %%%------------------------------------------------------------------- %%% File: slide_uniform_eqc.erl %%% @author Russell Brown %%% @doc quickcheck test for the folsom_sample_slide.erl %%% @end %%%------------------------------------------------------------------ -module(slide_uniform_eqc). -compile(export_all). -ifdef(TEST). -ifdef(EQC). -include("folsom.hrl"). -include_lib("eqc/include/eqc.hrl"). -include_lib("eqc/include/eqc_statem.hrl"). -include_lib("eunit/include/eunit.hrl"). -define(NUMTESTS, 200). -define(QC_OUT(P), eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)). -define(WINDOW, 60). -define(SIZE, 5). -record(state, {moment=1000, sample, name, count=orddict:new(), values=[]}). initial_state() -> meck:expect(folsom_utils, now_epoch, fun(_Now) -> 1000 end), meck:expect(folsom_utils, now_epoch, fun() -> 1000 end), #state{}. command(S) -> oneof( [{call, ?MODULE, new_histo, []} || S#state.sample == undefined] ++ [{call, ?MODULE, tick, [S#state.moment]} || S#state.sample /= undefined] ++ [{call, ?MODULE, update, [S#state.sample, int()]} || S#state.sample /= undefined] ++ [{call, ?MODULE, trim, [S#state.sample, ?WINDOW]} || S#state.sample /= undefined] ++ [{call, ?MODULE, get_values, [S#state.sample]} || S#state.sample /= undefined] ). %% Next state transformation, S is the current state next_state(S, V, {call, ?MODULE, new_histo, []}) -> S#state{name={call, erlang, element, [1, V]}, sample={call, erlang, element, [2, V]}}; next_state(S, V, {call, ?MODULE, tick, [_Moment]}) -> S#state{moment=V}; next_state(#state{moment=Moment, values=Values0, sample=Sample, count=Count}=S, NewSample, {call, ?MODULE, update, [_, Val]}) -> S#state{values={call, slide_uniform_eqc, new_state_values, [Sample, Moment, Values0, Val, Count]}, count={call, orddict, update_counter, [Moment, 1, Count]}, sample=NewSample}; next_state(#state{values=Values, moment=Moment}=S, _V, {call, ?MODULE, trim, _}) -> %% trim the model S#state{values={call, ?MODULE, trim, [Values, Moment, ?WINDOW]}}; next_state(S,_V,{call, ?MODULE, _, _}) -> S. %% Precondition, checked before command is added to the command sequence precondition(S, {call, _, new_histo, _}) -> S#state.sample == undefined; precondition(S, _) when S#state.sample == undefined -> false; precondition(_S, {call, _, _, _}) -> true. %% Postcondition, checked after command has been evaluated %% OBS: S is the state before next_state(S,_,) postcondition(#state{values=Values0, moment=Moment}, {call, ?MODULE, get_values, _}, Res) -> Values = [V || {{M, _C}, V} <- Values0, M >= Moment - ?WINDOW], case lists:sort(Values) == lists:sort(Res) of true -> true; _ -> {"get values", {"model", lists:sort(Values)}, {"sample", lists:sort(Res)}} end; postcondition(#state{values=Values, sample=Sample, moment=Moment}, {call, ?MODULE, trim, _}, _TrimCnt) -> %% check that values and the actual table contents are the same after a trim Table = [ Elem || {K, _V}=Elem <- ets:tab2list(Sample#slide_uniform.reservoir) , is_tuple(K)], %% filter out counter, case lists:sort(trim(Values, Moment, ?WINDOW)) == lists:sort(Table) of true -> true; _ -> {"after trim", {"model", lists:sort(trim(Values, Moment, ?WINDOW))}, {"sample", lists:sort(Table)}} end; postcondition(_S, {call, ?MODULE, _, _}, _Res) -> true. prop_window_test_() -> {setup, fun() -> ok end, fun(_X) -> (catch meck:unload(folsom_utils)), folsom:stop() end, fun(_X) -> {timeout, 30, ?_assert(eqc:quickcheck(eqc:numtests(?NUMTESTS, ?QC_OUT(prop_window()))))} end}. prop_window() -> folsom:start(), (catch meck:new(folsom_utils)), (catch meck:expect(folsom_utils, update_counter, fun(Tid, Key, Value) -> meck:passthrough([Tid, Key, Value]) end)), (catch meck:expect(folsom_utils, timestamp, fun() -> Res = os:timestamp(), put(timestamp, Res), Res end)), ?FORALL(Cmds, commands(?MODULE), aggregate(command_names(Cmds), begin {H, S, Res} = run_commands(?MODULE, Cmds), {Actual, Expected, Tab} = case S#state.sample of undefined -> {S#state.values, [], []}; Sample -> A = folsom_metrics:get_metric_value(S#state.name), E = [V || {{M, _C}, V} <- S#state.values, M >= S#state.moment - ?WINDOW], T = ets:tab2list(Sample#slide_uniform.reservoir), folsom_metrics:delete_metric(S#state.name), {A, E, T} end, ?WHENFAIL( io:format("History: ~p~nState: ~p~nActual: ~p~nExpected: ~p~nRes: ~p~nTab: ~p~n", [H, S, Actual, Expected, Res, Tab]), conjunction([{total, equals(lists:sort(Actual), lists:sort(Expected))}, {eq, equals(Res, ok)}])) end)). %% Commands new_histo() -> Ref = make_ref(), folsom_metrics:new_histogram(Ref, slide_uniform, {?WINDOW, ?SIZE}), #histogram{sample=Slide} = folsom_metrics_histogram:get_value(Ref), ok = folsom_sample_slide_server:stop(Slide#slide_uniform.server), {Ref, Slide}. tick(Moment) -> IncrBy = trunc(random:uniform(10)), meck:expect(folsom_utils, now_epoch, fun() -> Moment + IncrBy end), meck:expect(folsom_utils, now_epoch, fun(_Now) -> Moment + IncrBy end), Moment+IncrBy. update(Sample, Val) -> folsom_sample_slide_uniform:update(Sample, Val). trim(Sample, Window) -> folsom_sample_slide_uniform:trim(Sample#slide_uniform.reservoir, Window). get_values(Sample) -> folsom_sample_slide_uniform:get_values(Sample). %% private trim(L, Moment, Window) -> [{K, V} || {{M, _C}=K, V} <- L, M >= Moment - Window]. new_state_values(_Sample, Moment, Values, Val, Count) -> %Cnt = length([true || {{M, _C}, _V} <- Values, M == Moment]), Cnt = case orddict:find(Moment, Count) of error -> 1; {ok, V} -> V+1 end, case Cnt > ?SIZE of true -> %% replace {Rnd, _} = random:uniform_s(Cnt, get(timestamp)), case Rnd =< ?SIZE of true -> lists:keyreplace({Moment, Rnd}, 1, Values, {{Moment, Rnd}, Val}); false -> Values end; false -> %% insert Values ++ [{{Moment, Cnt}, Val}] end. -endif. -endif.