Skip to content

Commit e899115

Browse files
authored
RCBC-526: Add support for meters (#197)
1 parent cf43bda commit e899115

23 files changed

+1069
-13
lines changed

couchbase.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,6 @@ Gem::Specification.new do |spec|
6767
spec.extensions = ["ext/extconf.rb"]
6868
spec.rdoc_options << "--exclude" << "ext/"
6969

70+
spec.add_dependency "concurrent-ruby", "~> 1.3"
7071
spec.add_dependency "grpc", "~> 1.59"
7172
end

ext/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ add_library(
6060
rcb_utils.cxx
6161
rcb_version.cxx
6262
rcb_views.cxx
63-
rcb_observability.cxx)
63+
rcb_observability.cxx
64+
rcb_hdr_histogram.cxx)
6465
target_include_directories(couchbase PRIVATE ${PROJECT_BINARY_DIR}/generated)
6566
target_include_directories(
6667
couchbase
@@ -77,6 +78,7 @@ target_link_libraries(
7778
asio
7879
taocpp::json
7980
spdlog::spdlog
81+
hdr_histogram_static
8082
snappy)
8183
if(RUBY_LIBRUBY)
8284
target_link_directories(couchbase PRIVATE "${RUBY_LIBRARY_DIR}")

ext/couchbase.cxx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "rcb_diagnostics.hxx"
2626
#include "rcb_exceptions.hxx"
2727
#include "rcb_extras.hxx"
28+
#include "rcb_hdr_histogram.hxx"
2829
#include "rcb_logger.hxx"
2930
#include "rcb_multi.hxx"
3031
#include "rcb_query.hxx"
@@ -64,5 +65,6 @@ Init_libcouchbase(void)
6465
couchbase::ruby::init_diagnostics(cBackend);
6566
couchbase::ruby::init_extras(cBackend);
6667
couchbase::ruby::init_logger_methods(cBackend);
68+
couchbase::ruby::init_hdr_histogram(mCouchbase);
6769
}
6870
}

ext/rcb_backend.cxx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -426,15 +426,7 @@ initialize_cluster_options(const core::utils::connection_string& connstr,
426426
cluster_options.tracing().tracer(
427427
std::make_shared<couchbase::core::tracing::wrapper_sdk_tracer>());
428428

429-
static const auto sym_enable_metrics = rb_id2sym(rb_intern("enable_metrics"));
430-
if (auto param = options::get_bool(options, sym_enable_metrics); param) {
431-
cluster_options.metrics().enable(param.value());
432-
}
433-
434-
static const auto sym_metrics_emit_interval = rb_id2sym(rb_intern("metrics_emit_interval"));
435-
if (auto param = options::get_milliseconds(options, sym_metrics_emit_interval); param) {
436-
cluster_options.metrics().emit_interval(param.value());
437-
}
429+
cluster_options.metrics().enable(false); // Metrics are handled on the wrapper-side
438430

439431
static const auto sym_app_telemetry = rb_id2sym(rb_intern("application_telemetry"));
440432
if (auto app_telemetry_options = options::get_hash(options, sym_app_telemetry);

ext/rcb_hdr_histogram.cxx

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2+
/*
3+
* Copyright 2025-Present Couchbase, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
#include "rcb_hdr_histogram.hxx"
19+
#include "rcb_exceptions.hxx"
20+
21+
#include <hdr/hdr_histogram.h>
22+
#include <ruby.h>
23+
24+
#include <mutex>
25+
#include <shared_mutex>
26+
#include <vector>
27+
28+
namespace couchbase::ruby
29+
{
30+
namespace
31+
{
32+
struct cb_hdr_histogram_data {
33+
hdr_histogram* histogram{ nullptr };
34+
std::shared_mutex mutex{};
35+
};
36+
37+
void
38+
cb_hdr_histogram_close(cb_hdr_histogram_data* hdr_histogram_data)
39+
{
40+
if (hdr_histogram_data->histogram != nullptr) {
41+
hdr_close(hdr_histogram_data->histogram);
42+
hdr_histogram_data->histogram = nullptr;
43+
}
44+
}
45+
46+
void
47+
cb_HdrHistogramC_mark(void* /* ptr */)
48+
{
49+
/* no embedded ruby objects -- no mark */
50+
}
51+
52+
void
53+
cb_HdrHistogramC_free(void* ptr)
54+
{
55+
auto* hdr_histogram_data = static_cast<cb_hdr_histogram_data*>(ptr);
56+
cb_hdr_histogram_close(hdr_histogram_data);
57+
hdr_histogram_data->~cb_hdr_histogram_data();
58+
ruby_xfree(hdr_histogram_data);
59+
}
60+
61+
std::size_t
62+
cb_HdrHistogramC_memsize(const void* ptr)
63+
{
64+
const auto* hdr_histogram_data = static_cast<const cb_hdr_histogram_data*>(ptr);
65+
return sizeof(*hdr_histogram_data);
66+
}
67+
68+
const rb_data_type_t cb_hdr_histogram_type{
69+
"Couchbase/Utils/HdrHistogramC",
70+
{
71+
cb_HdrHistogramC_mark,
72+
cb_HdrHistogramC_free,
73+
cb_HdrHistogramC_memsize,
74+
// only one reserved field when GC.compact implemented
75+
#ifdef T_MOVED
76+
nullptr,
77+
#endif
78+
{},
79+
},
80+
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
81+
nullptr,
82+
nullptr,
83+
RUBY_TYPED_FREE_IMMEDIATELY,
84+
#endif
85+
};
86+
87+
VALUE
88+
cb_HdrHistogramC_allocate(VALUE klass)
89+
{
90+
cb_hdr_histogram_data* hdr_histogram = nullptr;
91+
VALUE obj =
92+
TypedData_Make_Struct(klass, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
93+
new (hdr_histogram) cb_hdr_histogram_data();
94+
return obj;
95+
}
96+
97+
VALUE
98+
cb_HdrHistogramC_initialize(VALUE self,
99+
VALUE lowest_discernible_value,
100+
VALUE highest_trackable_value,
101+
VALUE significant_figures)
102+
{
103+
Check_Type(lowest_discernible_value, T_FIXNUM);
104+
Check_Type(highest_trackable_value, T_FIXNUM);
105+
Check_Type(significant_figures, T_FIXNUM);
106+
107+
std::int64_t lowest = NUM2LL(lowest_discernible_value);
108+
std::int64_t highest = NUM2LL(highest_trackable_value);
109+
int sigfigs = NUM2INT(significant_figures);
110+
111+
cb_hdr_histogram_data* hdr_histogram;
112+
TypedData_Get_Struct(self, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
113+
114+
int res;
115+
{
116+
const std::unique_lock lock(hdr_histogram->mutex);
117+
res = hdr_init(lowest, highest, sigfigs, &hdr_histogram->histogram);
118+
}
119+
if (res != 0) {
120+
rb_raise(exc_couchbase_error(), "failed to initialize HDR histogram");
121+
return self;
122+
}
123+
124+
return self;
125+
}
126+
127+
VALUE
128+
cb_HdrHistogramC_close(VALUE self)
129+
{
130+
cb_hdr_histogram_data* hdr_histogram;
131+
TypedData_Get_Struct(self, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
132+
{
133+
const std::unique_lock lock(hdr_histogram->mutex);
134+
cb_hdr_histogram_close(hdr_histogram);
135+
}
136+
return Qnil;
137+
}
138+
139+
VALUE
140+
cb_HdrHistogramC_record_value(VALUE self, VALUE value)
141+
{
142+
Check_Type(value, T_FIXNUM);
143+
144+
std::int64_t val = NUM2LL(value);
145+
146+
cb_hdr_histogram_data* hdr_histogram;
147+
TypedData_Get_Struct(self, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
148+
149+
{
150+
const std::shared_lock lock(hdr_histogram->mutex);
151+
hdr_record_value_atomic(hdr_histogram->histogram, val);
152+
}
153+
return Qnil;
154+
}
155+
156+
VALUE
157+
cb_HdrHistogramC_get_percentiles_and_reset(VALUE self, VALUE percentiles)
158+
{
159+
Check_Type(percentiles, T_ARRAY);
160+
161+
cb_hdr_histogram_data* hdr_histogram;
162+
TypedData_Get_Struct(self, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
163+
164+
std::vector<std::int64_t> percentile_values{};
165+
std::int64_t total_count;
166+
{
167+
const std::unique_lock lock(hdr_histogram->mutex);
168+
total_count = hdr_histogram->histogram->total_count;
169+
for (std::size_t i = 0; i < static_cast<std::size_t>(RARRAY_LEN(percentiles)); ++i) {
170+
VALUE entry = rb_ary_entry(percentiles, static_cast<long>(i));
171+
Check_Type(entry, T_FLOAT);
172+
double perc = NUM2DBL(entry);
173+
std::int64_t value_at_perc = hdr_value_at_percentile(hdr_histogram->histogram, perc);
174+
percentile_values.push_back(value_at_perc);
175+
}
176+
hdr_reset(hdr_histogram->histogram);
177+
}
178+
179+
static const VALUE sym_total_count = rb_id2sym(rb_intern("total_count"));
180+
static const VALUE sym_percentiles = rb_id2sym(rb_intern("percentiles"));
181+
VALUE res = rb_hash_new();
182+
rb_hash_aset(res, sym_total_count, LL2NUM(total_count));
183+
VALUE perc_array = rb_ary_new_capa(static_cast<long>(percentile_values.size()));
184+
for (const auto& val : percentile_values) {
185+
rb_ary_push(perc_array, LL2NUM(val));
186+
}
187+
rb_hash_aset(res, sym_percentiles, perc_array);
188+
return res;
189+
}
190+
191+
VALUE
192+
cb_HdrHistogramC_bin_count(VALUE self)
193+
{
194+
cb_hdr_histogram_data* hdr_histogram;
195+
TypedData_Get_Struct(self, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
196+
197+
std::int32_t bin_count;
198+
{
199+
const std::unique_lock lock(hdr_histogram->mutex);
200+
bin_count = hdr_histogram->histogram->bucket_count;
201+
}
202+
return LONG2NUM(bin_count);
203+
}
204+
} // namespace
205+
206+
void
207+
init_hdr_histogram(VALUE mCouchbase)
208+
{
209+
VALUE mUtils = rb_define_module_under(mCouchbase, "Utils");
210+
VALUE cHdrHistogramC = rb_define_class_under(mUtils, "HdrHistogramC", rb_cObject);
211+
rb_define_alloc_func(cHdrHistogramC, cb_HdrHistogramC_allocate);
212+
rb_define_method(cHdrHistogramC, "initialize", cb_HdrHistogramC_initialize, 3);
213+
rb_define_method(cHdrHistogramC, "close", cb_HdrHistogramC_close, 0);
214+
rb_define_method(cHdrHistogramC, "record_value", cb_HdrHistogramC_record_value, 1);
215+
rb_define_method(cHdrHistogramC, "bin_count", cb_HdrHistogramC_bin_count, 0);
216+
rb_define_method(
217+
cHdrHistogramC, "get_percentiles_and_reset", cb_HdrHistogramC_get_percentiles_and_reset, 1);
218+
}
219+
} // namespace couchbase::ruby

ext/rcb_hdr_histogram.hxx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2+
/*
3+
* Copyright 2025-Present Couchbase, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
#ifndef COUCHBASE_RUBY_RCB_HDR_HISTOGRAM_HXX
19+
#define COUCHBASE_RUBY_RCB_HDR_HISTOGRAM_HXX
20+
21+
#include <ruby/internal/value.h>
22+
23+
namespace couchbase::ruby
24+
{
25+
void
26+
init_hdr_histogram(VALUE mCouchbase);
27+
} // namespace couchbase::ruby
28+
#endif // COUCHBASE_RUBY_RCB_HDR_HISTOGRAM_HXX

lib/couchbase/cluster.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@
2929

3030
require "couchbase/protostellar"
3131
require "couchbase/utils/observability"
32+
3233
require "couchbase/tracing/threshold_logging_tracer"
3334
require "couchbase/tracing/noop_tracer"
35+
require "couchbase/metrics/noop_meter"
36+
require "couchbase/metrics/logging_meter"
3437

3538
module Couchbase
3639
# The main entry point when connecting to a Couchbase cluster.
@@ -374,6 +377,7 @@ def initialize(connection_string, *args)
374377
raise ArgumentError, "missing password" unless credentials[:password]
375378
when Options::Cluster
376379
tracer = options.tracer
380+
meter = options.meter
377381
open_options = options.to_backend || {}
378382
authenticator = options.authenticator
379383
case authenticator
@@ -401,7 +405,7 @@ def initialize(connection_string, *args)
401405
end
402406

403407
@observability = Observability::Wrapper.new do |w|
404-
w.tracer = if !(open_options[:enable_tracing].nil? && !open_options[:enable_tracing])
408+
w.tracer = if !open_options[:enable_tracing].nil? && !open_options[:enable_tracing]
405409
Tracing::NoopTracer.new
406410
elsif tracer.nil?
407411
Tracing::ThresholdLoggingTracer.new(
@@ -417,6 +421,15 @@ def initialize(connection_string, *args)
417421
else
418422
tracer
419423
end
424+
w.meter = if !open_options[:enable_metrics].nil? && !open_options[:enable_metrics]
425+
Metrics::NoopMeter.new
426+
elsif meter.nil?
427+
Metrics::LoggingMeter.new(
428+
emit_interval: open_options[:metrics_emit_interval],
429+
)
430+
else
431+
meter
432+
end
420433
end
421434

422435
@backend = Backend.new

0 commit comments

Comments
 (0)