Skip to content

Commit c1a57bf

Browse files
committed
RCBC-526: Add support for meters
1 parent c6a8af1 commit c1a57bf

27 files changed

+1069
-21
lines changed

Rakefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ task :cache_cxx_dependencies do
110110
"-DCOUCHBASE_CXX_CLIENT_BUILD_TOOLS=OFF",
111111
"-DCOUCHBASE_CXX_CLIENT_BUILD_DOCS=OFF",
112112
"-DCOUCHBASE_CXX_CLIENT_STATIC_BORINGSSL=ON",
113+
"-DCOUCHBASE_CXX_CLIENT_BUILD_OPENTELEMETRY=OFF",
113114
"-DCPM_DOWNLOAD_ALL=ON",
114115
"-DCPM_USE_NAMED_CACHE_DIRECTORIES=ON",
115116
"-DCPM_USE_LOCAL_PACKAGES=OFF",
@@ -226,6 +227,7 @@ task :cache_cxx_dependencies do
226227
"-DCPM_USE_LOCAL_PACKAGES=OFF",
227228
"-DCPM_SOURCE_CACHE=#{cpm_cache_dir}",
228229
"-DCOUCHBASE_CXX_CLIENT_EMBED_MOZILLA_CA_BUNDLE_ROOT=#{cpm_cache_dir}",
230+
"-DCOUCHBASE_CXX_CLIENT_BUILD_OPENTELEMETRY=OFF",
229231
]
230232
cmake_flags << "-DCMAKE_C_COMPILER=#{cc}" if cc
231233
cmake_flags << "-DCMAKE_CXX_COMPILER=#{cxx}" if cxx

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

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/extconf.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ def sys(*cmd)
9797
"-DCOUCHBASE_CXX_CLIENT_BUILD_TOOLS=OFF",
9898
"-DCOUCHBASE_CXX_CLIENT_BUILD_EXAMPLES=OFF",
9999
"-DCOUCHBASE_CXX_CLIENT_INSTALL=OFF",
100+
"-DCOUCHBASE_CXX_CLIENT_BUILD_OPENTELEMETRY=OFF",
100101
]
101102

102103
if version.start_with?("4")

ext/rcb_hdr_histogram.cxx

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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 <shared_mutex>
25+
#include <vector>
26+
27+
namespace couchbase::ruby
28+
{
29+
namespace
30+
{
31+
struct cb_hdr_histogram_data {
32+
hdr_histogram* histogram{ nullptr };
33+
std::shared_mutex mutex{};
34+
};
35+
36+
void
37+
cb_hdr_histogram_close(cb_hdr_histogram_data* hdr_histogram_data)
38+
{
39+
if (hdr_histogram_data->histogram != nullptr) {
40+
hdr_close(hdr_histogram_data->histogram);
41+
hdr_histogram_data->histogram = nullptr;
42+
}
43+
}
44+
45+
void
46+
cb_HdrHistogramC_mark(void* /* ptr */)
47+
{
48+
/* no embedded ruby objects -- no mark */
49+
}
50+
51+
void
52+
cb_HdrHistogramC_free(void* ptr)
53+
{
54+
auto* hdr_histogram_data = static_cast<cb_hdr_histogram_data*>(ptr);
55+
cb_hdr_histogram_close(hdr_histogram_data);
56+
hdr_histogram_data->~cb_hdr_histogram_data();
57+
ruby_xfree(hdr_histogram_data);
58+
}
59+
60+
std::size_t
61+
cb_HdrHistogramC_memsize(const void* ptr)
62+
{
63+
const auto* hdr_histogram_data = static_cast<const cb_hdr_histogram_data*>(ptr);
64+
return sizeof(*hdr_histogram_data);
65+
}
66+
67+
const rb_data_type_t cb_hdr_histogram_type{
68+
"Couchbase/Utils/HdrHistogramC",
69+
{
70+
cb_HdrHistogramC_mark,
71+
cb_HdrHistogramC_free,
72+
cb_HdrHistogramC_memsize,
73+
// only one reserved field when GC.compact implemented
74+
#ifdef T_MOVED
75+
nullptr,
76+
#endif
77+
{},
78+
},
79+
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
80+
nullptr,
81+
nullptr,
82+
RUBY_TYPED_FREE_IMMEDIATELY,
83+
#endif
84+
};
85+
86+
VALUE
87+
cb_HdrHistogramC_allocate(VALUE klass)
88+
{
89+
cb_hdr_histogram_data* hdr_histogram = nullptr;
90+
VALUE obj =
91+
TypedData_Make_Struct(klass, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
92+
new (hdr_histogram) cb_hdr_histogram_data();
93+
return obj;
94+
}
95+
96+
VALUE
97+
cb_HdrHistogramC_initialize(VALUE self,
98+
VALUE lowest_discernible_value,
99+
VALUE highest_trackable_value,
100+
VALUE significant_figures)
101+
{
102+
Check_Type(lowest_discernible_value, T_FIXNUM);
103+
Check_Type(highest_trackable_value, T_FIXNUM);
104+
Check_Type(significant_figures, T_FIXNUM);
105+
106+
std::int64_t lowest = NUM2LL(lowest_discernible_value);
107+
std::int64_t highest = NUM2LL(highest_trackable_value);
108+
int sigfigs = NUM2INT(significant_figures);
109+
110+
cb_hdr_histogram_data* hdr_histogram;
111+
TypedData_Get_Struct(self, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
112+
113+
int res;
114+
{
115+
const std::unique_lock lock(hdr_histogram->mutex);
116+
res = hdr_init(lowest, highest, sigfigs, &hdr_histogram->histogram);
117+
}
118+
if (res != 0) {
119+
rb_raise(exc_couchbase_error(), "failed to initialize HDR histogram");
120+
return self;
121+
}
122+
123+
return self;
124+
}
125+
126+
VALUE
127+
cb_HdrHistogramC_close(VALUE self)
128+
{
129+
cb_hdr_histogram_data* hdr_histogram;
130+
TypedData_Get_Struct(self, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
131+
{
132+
const std::unique_lock lock(hdr_histogram->mutex);
133+
cb_hdr_histogram_close(hdr_histogram);
134+
}
135+
return Qnil;
136+
}
137+
138+
VALUE
139+
cb_HdrHistogramC_record_value(VALUE self, VALUE value)
140+
{
141+
Check_Type(value, T_FIXNUM);
142+
143+
std::int64_t val = NUM2LL(value);
144+
145+
cb_hdr_histogram_data* hdr_histogram;
146+
TypedData_Get_Struct(self, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
147+
148+
{
149+
const std::shared_lock lock(hdr_histogram->mutex);
150+
hdr_record_value_atomic(hdr_histogram->histogram, val);
151+
}
152+
return Qnil;
153+
}
154+
155+
VALUE
156+
cb_HdrHistogramC_get_percentiles_and_reset(VALUE self, VALUE percentiles)
157+
{
158+
Check_Type(percentiles, T_ARRAY);
159+
160+
cb_hdr_histogram_data* hdr_histogram;
161+
TypedData_Get_Struct(self, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
162+
163+
std::vector<std::int64_t> percentile_values{};
164+
std::int64_t total_count;
165+
{
166+
const std::unique_lock lock(hdr_histogram->mutex);
167+
total_count = hdr_histogram->histogram->total_count;
168+
for (std::size_t i = 0; i < static_cast<std::size_t>(RARRAY_LEN(percentiles)); ++i) {
169+
VALUE entry = rb_ary_entry(percentiles, static_cast<long>(i));
170+
Check_Type(entry, T_FLOAT);
171+
double perc = NUM2DBL(entry);
172+
std::int64_t value_at_perc = hdr_value_at_percentile(hdr_histogram->histogram, perc);
173+
percentile_values.push_back(value_at_perc);
174+
}
175+
hdr_reset(hdr_histogram->histogram);
176+
}
177+
178+
static const VALUE sym_total_count = rb_id2sym(rb_intern("total_count"));
179+
static const VALUE sym_percentiles = rb_id2sym(rb_intern("percentiles"));
180+
VALUE res = rb_hash_new();
181+
rb_hash_aset(res, sym_total_count, LL2NUM(total_count));
182+
VALUE perc_array = rb_ary_new_capa(static_cast<long>(percentile_values.size()));
183+
for (const auto& val : percentile_values) {
184+
rb_ary_push(perc_array, LL2NUM(val));
185+
}
186+
rb_hash_aset(res, sym_percentiles, perc_array);
187+
return res;
188+
}
189+
190+
VALUE
191+
cb_HdrHistogramC_bin_count(VALUE self)
192+
{
193+
cb_hdr_histogram_data* hdr_histogram;
194+
TypedData_Get_Struct(self, cb_hdr_histogram_data, &cb_hdr_histogram_type, hdr_histogram);
195+
196+
std::int32_t bin_count;
197+
{
198+
const std::unique_lock lock(hdr_histogram->mutex);
199+
bin_count = hdr_histogram->histogram->bucket_count;
200+
}
201+
return LONG2NUM(bin_count);
202+
}
203+
} // namespace
204+
205+
void
206+
init_hdr_histogram(VALUE mCouchbase)
207+
{
208+
VALUE mUtils = rb_define_module_under(mCouchbase, "Utils");
209+
VALUE cHdrHistogramC = rb_define_class_under(mUtils, "HdrHistogramC", rb_cObject);
210+
rb_define_alloc_func(cHdrHistogramC, cb_HdrHistogramC_allocate);
211+
rb_define_method(cHdrHistogramC, "initialize", cb_HdrHistogramC_initialize, 3);
212+
rb_define_method(cHdrHistogramC, "close", cb_HdrHistogramC_close, 0);
213+
rb_define_method(cHdrHistogramC, "record_value", cb_HdrHistogramC_record_value, 1);
214+
rb_define_method(cHdrHistogramC, "bin_count", cb_HdrHistogramC_bin_count, 0);
215+
rb_define_method(
216+
cHdrHistogramC, "get_percentiles_and_reset", cb_HdrHistogramC_get_percentiles_and_reset, 1);
217+
}
218+
} // 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: 29 additions & 14 deletions
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,20 +405,31 @@ def initialize(connection_string, *args)
401405
end
402406

403407
@observability = Observability::Wrapper.new do |w|
404-
if !(open_options[:enable_tracing].nil? && !open_options[:enable_tracing])
405-
w.tracer = Tracing::NoopTracer.new
406-
elsif tracer.nil?
407-
w.tracer = Tracing::ThresholdLoggingTracer.new(
408-
emit_interval: open_options[:threshold_emit_interval],
409-
kv_threshold: open_options[:key_value_threshold],
410-
query_threshold: open_options[:query_threshold],
411-
views_threshold: open_options[:view_threshold],
412-
search_threshold: open_options[:search_threshold],
413-
analytics_threshold: open_options[:analytics_threshold],
414-
management_threshold: open_options[:management_threshold],
415-
sample_size: open_options[:threshold_sample_size],
416-
)
417-
end
408+
w.tracer = if !(open_options[:enable_tracing].nil? && !open_options[:enable_tracing])
409+
Tracing::NoopTracer.new
410+
elsif tracer.nil?
411+
Tracing::ThresholdLoggingTracer.new(
412+
emit_interval: open_options[:threshold_emit_interval],
413+
kv_threshold: open_options[:key_value_threshold],
414+
query_threshold: open_options[:query_threshold],
415+
views_threshold: open_options[:view_threshold],
416+
search_threshold: open_options[:search_threshold],
417+
analytics_threshold: open_options[:analytics_threshold],
418+
management_threshold: open_options[:management_threshold],
419+
sample_size: open_options[:threshold_sample_size],
420+
)
421+
else
422+
tracer
423+
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
418433
end
419434

420435
@backend = Backend.new

0 commit comments

Comments
 (0)