-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Log the estimate of batch metrics memory consumption #18916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
834859e
d1a129b
e14381a
2ac6354
de0ab9c
e575d84
5b90df1
ad19aa1
3faf8fe
df29ae5
b0f27a7
1ac40d5
b42faa4
6c91d6a
85c4f1e
0a513db
92c1627
d5c462d
34c4590
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ | |
| require "stud/try" | ||
| require 'timeout' | ||
| require "thread" | ||
| require 'java' | ||
|
|
||
| class DummyInput < LogStash::Inputs::Base | ||
| config_name "dummyinput" | ||
|
|
@@ -219,16 +220,21 @@ def flush(options) | |
| let(:pipeline_settings) do | ||
| { | ||
| "dead_letter_queue.enable" => dead_letter_queue_enabled, | ||
| "path.dead_letter_queue" => dead_letter_queue_path | ||
| "path.dead_letter_queue" => dead_letter_queue_path, | ||
| "pipeline.batch.metrics.sampling_mode" => batch_sampling_mode, | ||
| } | ||
| end | ||
| let(:batch_sampling_mode) { "disabled" } | ||
| let(:max_retry) {10} #times | ||
| let(:timeout) {120} #seconds | ||
|
|
||
| before :each do | ||
| pipeline_workers_setting = LogStash::SETTINGS.get_setting("pipeline.workers") | ||
| allow(pipeline_workers_setting).to receive(:default).and_return(worker_thread_count) | ||
|
|
||
| pipeline_workers_setting = pipeline_settings_obj.get_setting("pipeline.workers") | ||
| allow(pipeline_workers_setting).to receive(:default).and_return(worker_thread_count) | ||
|
|
||
| pipeline_settings.each {|k, v| pipeline_settings_obj.set(k, v) } | ||
|
estolfo marked this conversation as resolved.
|
||
| end | ||
|
|
||
|
|
@@ -390,7 +396,7 @@ def flush(options) | |
| mutate { add_tag => "miss" } | ||
| } | ||
| } | ||
| CONFIG | ||
| CONFIG | ||
|
|
||
| context "raise an error when it's evaluated, should cancel the event execution and log the error" do | ||
| context "when type of evaluation doesn't have same type" do | ||
|
|
@@ -429,7 +435,7 @@ def flush(options) | |
| } | ||
| } | ||
| } | ||
| CONFIG | ||
| CONFIG | ||
|
|
||
| sample_one( [{ "path" => {"to" => {"value" => "101"}}}] ) do | ||
| expect(subject).to be nil | ||
|
|
@@ -688,7 +694,7 @@ def flush(options) | |
| context "when there is no command line -w N set" do | ||
| it "starts one filter thread" do | ||
| msg = "Defaulting pipeline worker threads to 1 because there are some filters that might not work with multiple worker threads" | ||
| pipeline = mock_java_pipeline_from_string(test_config_with_filters) | ||
| pipeline = mock_java_pipeline_from_string(test_config_with_filters, pipeline_settings_obj) | ||
| expect(pipeline.logger).to receive(:warn).with(msg, | ||
| hash_including({:count_was => worker_thread_count, :filters => ["dummyfilter"]})) | ||
| pipeline.start | ||
|
|
@@ -772,7 +778,7 @@ def flush(options) | |
| } | ||
|
|
||
| context "input and output close" do | ||
| let(:pipeline) { mock_java_pipeline_from_string(test_config_without_output_workers) } | ||
| let(:pipeline) { mock_java_pipeline_from_string(test_config_without_output_workers, mock_settings("pipeline.batch.metrics.sampling_mode" => batch_sampling_mode)) } | ||
| let(:output) { pipeline.outputs.first } | ||
| let(:input) { pipeline.inputs.first } | ||
|
|
||
|
|
@@ -824,7 +830,7 @@ def flush(options) | |
| let(:config) { "input { dummyinput {} } output { dummyoutput {} }"} | ||
|
|
||
| it "should start the flusher thread only after the pipeline is running" do | ||
| pipeline = mock_java_pipeline_from_string(config) | ||
| pipeline = mock_java_pipeline_from_string(config, mock_settings("pipeline.batch.metrics.sampling_mode" => batch_sampling_mode)) | ||
|
|
||
| expect(pipeline).to receive(:transition_to_running).ordered.and_call_original | ||
| expect(pipeline).to receive(:start_flusher).ordered.and_call_original | ||
|
|
@@ -1049,7 +1055,7 @@ def flush(options) | |
| context "metrics" do | ||
| config = "input { } filter { } output { }" | ||
|
|
||
| let(:settings) { LogStash::SETTINGS.clone } | ||
| let(:settings) { mock_settings("pipeline.batch.metrics.sampling_mode" => batch_sampling_mode) } | ||
| subject { mock_java_pipeline_from_string(config, settings, metric) } | ||
|
|
||
| after :each do | ||
|
|
@@ -1132,6 +1138,56 @@ def flush(options) | |
| expect(subject.metric.collector).to be(collector) | ||
| end | ||
| end | ||
|
|
||
| context "batch structure metric estimation" do | ||
| let(:collector) { ::LogStash::Instrument::Collector.new } | ||
| let(:metric) { ::LogStash::Instrument::Metric.new(collector) } | ||
|
|
||
| let(:sample_occupation) do | ||
| java_import 'org.HdrHistogram.Recorder' | ||
| # HistogramMetric uses HdrHistogram with 3 digits precision, so create a sample to have | ||
| # the rough histogram memory consumption. | ||
| sample = Recorder.new(3).interval_histogram | ||
|
|
||
| sample.estimated_footprint_in_bytes | ||
| end | ||
|
|
||
| # BatchStructureMetric has 4 policies | ||
| # Each window contains also the staging, so | ||
| # has to be summed up to the bare count of retention / resolution periods. | ||
|
andsel marked this conversation as resolved.
|
||
| # Note that the calculations correspond to the resolutions per 60 seconds multiplied by the number | ||
| # of minutes. For example, for the 5 minute rentention policy: | ||
| # 60 / 15 (resolution per 60 seconds) * 5 (minutes). The + 1 corresponds to the staging datapoint. | ||
| let(:last_1_minute_datapoints) { 60 / 3 + 1 } | ||
| let(:last_5_minutes_datapoints) { 5 * 60 / 15 + 1} | ||
| let(:last_15_minutes_datapoints) { 15 * 60 / 30 + 1 } | ||
| let(:lifetime_datapoints) { 2 } | ||
| let(:single_batch_metric_datapoints) do | ||
| last_1_minute_datapoints + last_5_minutes_datapoints + last_15_minutes_datapoints + lifetime_datapoints | ||
| end | ||
|
|
||
| # byte size and event count batch metrics has single_batch_metric_datapoints each plus | ||
| # other 3 datapoints used by each lifetime histogram metric | ||
| let(:total_datapoints) { 2 * single_batch_metric_datapoints + 2 * 3 } | ||
| let(:expected_occupation) { sample_occupation * total_datapoints } | ||
|
|
||
| context "when enabled" do | ||
| # enable batch sampling else the batch metrics are not initialized | ||
| let(:batch_sampling_mode) { "full" } | ||
|
|
||
| it "should report the expected result" do | ||
| subject.initialize_flow_metrics | ||
| expect(subject.estimate_batch_metrics_occupation).to be(expected_occupation) | ||
| end | ||
| end | ||
|
|
||
| context "when disabled" do | ||
| it "must return nil" do | ||
| subject.initialize_flow_metrics | ||
| expect(subject.estimate_batch_metrics_occupation).to be_nil | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
|
|
@@ -1190,13 +1246,13 @@ def flush(options) | |
| end | ||
| end | ||
| context "Pipeline created with too many filters" do | ||
| # create pipeline with 2000 filters | ||
| # 2000 filters is more than a thread stack of size 2MB can handle | ||
| # create pipeline with 2500 filters | ||
| # 2500 filters is more than a thread stack of size 2MB can handle | ||
| let(:config) do | ||
| <<-EOS | ||
| input { dummy_input {} } | ||
| filter { | ||
| #{" nil_flushing_filter {}\n" * 2000} | ||
| #{" nil_flushing_filter {}\n" * 2500} | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note for reviewer |
||
| } | ||
| output { dummy_output {} } | ||
| EOS | ||
|
|
@@ -1368,7 +1424,7 @@ def flush(options) | |
| EOS | ||
| end | ||
|
|
||
| subject { mock_java_pipeline_from_string(config) } | ||
| subject { mock_java_pipeline_from_string(config, mock_settings("pipeline.batch.metrics.sampling_mode" => batch_sampling_mode)) } | ||
|
|
||
| context "when the pipeline is not started" do | ||
| after :each do | ||
|
|
@@ -1395,7 +1451,7 @@ def flush(options) | |
| } | ||
| EOS | ||
| end | ||
| subject { mock_java_pipeline_from_string(config) } | ||
| subject { mock_java_pipeline_from_string(config, mock_settings("pipeline.batch.metrics.sampling_mode" => batch_sampling_mode)) } | ||
|
|
||
| context "when the pipeline is not started" do | ||
| after :each do | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -355,6 +355,43 @@ public final IRubyObject openQueue(final ThreadContext context) { | |
| return context.nil; | ||
| } | ||
|
|
||
| /** | ||
| * Needs to be invoked after initialize_flow_metrics | ||
| * | ||
| * @return the occupation of internal structures in bytes or nil if batch metrics are disabled | ||
| * */ | ||
| @JRubyMethod(name = "estimate_batch_metrics_occupation") | ||
| public final IRubyObject estimateBatchMetricsInternalStructures(final ThreadContext context) { | ||
| if (!isBatchMetricsEnabled(context)) { | ||
| return context.nil; | ||
| } | ||
|
|
||
| return context.runtime.newFixnum(getEstimateBatchMetricsInternalStructures()); | ||
| } | ||
|
|
||
| private int getEstimateBatchMetricsInternalStructures() { | ||
| if (batchStructureMetric == null) { | ||
| throw new IllegalStateException("Batch metrics estimation invoked before the internal data structures were instantiated"); | ||
|
estolfo marked this conversation as resolved.
|
||
| } | ||
| int eventCountFootprint = batchEventCountStructureMetric.estimateBatchMetricsFootprintInBytes(); | ||
| int byteSizeFootprint = batchStructureMetric.estimateBatchMetricsFootprintInBytes(); | ||
| int histogramCollectorFootprint = filterQueueClient.estimateBatchMetricsFootprintInBytes(); | ||
| return eventCountFootprint + byteSizeFootprint + histogramCollectorFootprint; | ||
| } | ||
|
|
||
| @JRubyMethod(name = "log_batch_metrics_memory_consumption") | ||
| public final IRubyObject logEstimatedBatchMetricMemoryConsumption(final ThreadContext context) { | ||
| if (metric.collector(context).isNil() || !getSetting(context, "metric.collect").isTrue()) { | ||
| LOGGER.debug("Metrics collection is disabled, skipping batch metrics logging"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If metrics collection is disabled, should we still log that metrics logging will be skipped? Or would a user expect that if metrics collection is disabled, no logging related to metrics will be done?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If metric collection is disabled, and can be only programmatically disabled, like in the monitoring pipeline, then no batch metrics log line is expected. |
||
| return context.nil; | ||
| } | ||
|
|
||
| if (isBatchMetricsEnabled(context)) { | ||
| LOGGER.info("Pipeline `{}` batch metrics estimated memory consumption: {} bytes", pipelineId, getEstimateBatchMetricsInternalStructures()); | ||
| } | ||
| return context.nil; | ||
| } | ||
|
|
||
| @JRubyMethod(name = "process_events_namespace_metric") | ||
| public final IRubyObject processEventsNamespaceMetric(final ThreadContext context) { | ||
| return metric.namespace(context, EVENTS_METRIC_NAMESPACE); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.