Hazelcast C++ Client
Hazelcast C++ Client Library
Loading...
Searching...
No Matches
stats.cpp
1/*
2 * Copyright (c) 2008-2025, Hazelcast, Inc. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <boost/utility/string_view_fwd.hpp>
18#include <iomanip>
19
20#include <boost/algorithm/string/replace.hpp>
21#include <boost/utility/string_view.hpp>
22#include <sstream>
23
24#include "hazelcast/client/client_config.h"
25#include "hazelcast/client/client_properties.h"
26#include "hazelcast/client/connection/ClientConnectionManagerImpl.h"
27#include "hazelcast/client/connection/Connection.h"
28#include "hazelcast/client/impl/metrics/metrics_compressor.h"
29#include "hazelcast/client/impl/metrics/metric_descriptor.h"
30#include "hazelcast/client/impl/statistics/Statistics.h"
31#include "hazelcast/client/internal/nearcache/NearCache.h"
32#include "hazelcast/client/internal/nearcache/NearCacheManager.h"
33#include "hazelcast/client/monitor/impl/LocalMapStatsImpl.h"
34#include "hazelcast/client/monitor/impl/NearCacheStatsImpl.h"
35#include "hazelcast/client/protocol/codec/codecs.h"
36#include "hazelcast/client/protocol/codec/codecs.h"
37#include "hazelcast/client/spi/ClientContext.h"
38#include "hazelcast/client/spi/impl/ClientExecutionServiceImpl.h"
39#include "hazelcast/client/spi/impl/ClientInvocation.h"
40#include "hazelcast/client/spi/lifecycle_service.h"
41
42namespace hazelcast {
43namespace client {
44namespace impl {
45namespace statistics {
46const std::string Statistics::NEAR_CACHE_CATEGORY_PREFIX("nc.");
47
48Statistics::Statistics(spi::ClientContext& client_context)
49 : client_context_(client_context)
50 , client_properties_(client_context.get_client_properties())
51 , logger_(client_context.get_logger())
52 , periodic_stats_(*this)
53{
54 this->enabled_ = client_properties_.get_boolean(
55 client_properties_.get_statistics_enabled());
56}
57
58void
59Statistics::start()
60{
61 if (!enabled_) {
62 return;
63 }
64
65 int64_t periodSeconds = client_properties_.get_long(
66 client_properties_.get_statistics_period_seconds());
67 if (periodSeconds <= 0) {
68
69 int64_t defaultValue = util::IOUtil::to_value<int64_t>(
70 client_properties_.get_statistics_period_seconds()
71 .get_default_value());
72 HZ_LOG(
73 logger_,
74 warning,
75 boost::str(
76 boost::format("Provided client statistics %1% cannot be less than "
77 "or equal to 0. "
78 "You provided %2% seconds as the configuration. "
79 "Client will use the default value of %3% instead.") %
80 client_properties_.get_statistics_period_seconds().get_name() %
81 periodSeconds % defaultValue));
82 periodSeconds = defaultValue;
83 }
84
85 schedule_periodic_statistics_send_task(periodSeconds);
86
87 HZ_LOG(
88 logger_,
89 info,
90 boost::str(
91 boost::format("Client statistics is enabled with period %1% seconds.") %
92 periodSeconds));
93}
94
95void
96Statistics::shutdown()
97{
98 if (send_task_timer_) {
99 boost::system::error_code ignored;
100 send_task_timer_->cancel(ignored);
101 }
102}
103
104void
105Statistics::schedule_periodic_statistics_send_task(int64_t period_seconds)
106{
107 send_task_timer_ =
108 client_context_.get_client_execution_service().schedule_with_repetition(
109 [=]() {
110 if (!client_context_.get_lifecycle_service().is_running()) {
111 return;
112 }
113
114 auto collection_timestamp =
115 std::chrono::duration_cast<std::chrono::milliseconds>(
116 std::chrono::system_clock::now().time_since_epoch())
117 .count();
118 std::shared_ptr<connection::Connection> connection =
119 get_connection();
120 if (!connection) {
121 HZ_LOG(logger_,
122 finest,
123 "Cannot send client statistics to the server. No "
124 "connection found.");
125 return;
126 }
127
128 std::ostringstream stats;
129 metrics::metrics_compressor metrics_comp;
130
131 periodic_stats_.fill_metrics(stats, metrics_comp, connection);
132
133 periodic_stats_.add_near_cache_stats(stats, metrics_comp);
134
135 send_stats(collection_timestamp,
136 stats.str(),
137 metrics_comp.get_blob(),
138 connection);
139 },
140 std::chrono::seconds(0),
141 std::chrono::seconds(period_seconds));
142}
143
144std::shared_ptr<connection::Connection>
145Statistics::get_connection()
146{
147 return client_context_.get_connection_manager().get_random_connection();
148}
149
150void
151Statistics::send_stats(
152 int64_t timestamp,
153 const std::string& new_stats,
154 const std::vector<byte> metrics_blob,
155 const std::shared_ptr<connection::Connection>& connection)
156{
157 auto request = protocol::codec::client_statistics_encode(
158 timestamp, new_stats, metrics_blob);
159 try {
160 spi::impl::ClientInvocation::create(
161 client_context_, request, "", connection)
162 ->invoke()
163 .get();
164 } catch (exception::iexception& e) {
165 // suppress exception, do not print too many messages
166 HZ_LOG(logger_,
167 finest,
168 boost::str(boost::format("Could not send stats %1%") % e));
169 }
170}
171
172void
173Statistics::PeriodicStatistics::fill_metrics(
174 std::ostringstream& stats,
175 metrics::metrics_compressor& compressor,
176 const std::shared_ptr<connection::Connection>& connection)
177{
178 stats << "lastStatisticsCollectionTime" << KEY_VALUE_SEPARATOR
179 << util::current_time_millis();
180 add_stat(stats, "enterprise", false);
181 add_stat(stats, "clientType", protocol::ClientTypes::CPP);
182 add_stat(stats, "clientVersion", HAZELCAST_VERSION);
183 add_stat(stats,
184 "clusterConnectionTimestamp",
185 std::chrono::duration_cast<std::chrono::milliseconds>(
186 connection->get_start_time().time_since_epoch())
187 .count());
188
189 auto localSocketAddress = connection->get_local_socket_address();
190 stats << STAT_SEPARATOR << "clientAddress" << KEY_VALUE_SEPARATOR;
191 if (localSocketAddress) {
192 stats << localSocketAddress->get_host() << ":"
193 << localSocketAddress->get_port();
194 }
195
196 add_stat(stats, "clientName", statistics_.client_context_.get_name());
197
198 auto credential =
199 statistics_.client_context_.get_client_config().get_credentials();
200 if (credential) {
201 add_stat(stats, "credentials.principal", credential->name());
202 }
203
204 auto hw_concurrency = std::thread::hardware_concurrency();
205 // necessary for compatibility with Management Center 4.0
206 add_stat(stats, "runtime.availableProcessors", hw_concurrency);
207 compressor.add_long(
208 { "runtime", "availableProcessors", metrics::probe_unit::COUNT },
209 hw_concurrency);
210
211 // more gauges can be added here
212}
213
214void
215Statistics::PeriodicStatistics::add_near_cache_stats(
216 std::ostringstream& stats,
217 metrics::metrics_compressor& compressor)
218{
219 for (auto near_cache : statistics_.client_context_.get_near_cache_manager()
220 .list_all_near_caches()) {
221 std::string nc_name = near_cache->get_name();
222
223 std::ostringstream nc_name_with_prefix_strm;
224 get_name_with_prefix(nc_name, nc_name_with_prefix_strm);
225 nc_name_with_prefix_strm << '.';
226 std::string nc_name_with_prefix = nc_name_with_prefix_strm.str();
227
228 auto nc_stats =
229 std::static_pointer_cast<monitor::impl::NearCacheStatsImpl>(
230 near_cache->get_near_cache_stats());
231
232 add_near_cache_metric(stats,
233 compressor,
234 "creationTime",
235 nc_name,
236 nc_name_with_prefix,
237 nc_stats->get_creation_time(),
238 metrics::probe_unit::MS);
239
240 add_near_cache_metric(stats,
241 compressor,
242 "evictions",
243 nc_name,
244 nc_name_with_prefix,
245 nc_stats->get_evictions(),
246 metrics::probe_unit::COUNT);
247
248 add_near_cache_metric(stats,
249 compressor,
250 "hits",
251 nc_name,
252 nc_name_with_prefix,
253 nc_stats->get_hits(),
254 metrics::probe_unit::COUNT);
255
256 add_near_cache_metric(stats,
257 compressor,
258 "lastPersistenceDuration",
259 nc_name,
260 nc_name_with_prefix,
261 nc_stats->get_last_persistence_duration(),
262 metrics::probe_unit::MS);
263
264 add_near_cache_metric(stats,
265 compressor,
266 "lastPersistenceKeyCount",
267 nc_name,
268 nc_name_with_prefix,
269 nc_stats->get_last_persistence_key_count(),
270 metrics::probe_unit::COUNT);
271
272 add_near_cache_metric(stats,
273 compressor,
274 "lastPersistenceTime",
275 nc_name,
276 nc_name_with_prefix,
277 nc_stats->get_last_persistence_time(),
278 metrics::probe_unit::MS);
279
280 add_near_cache_metric(stats,
281 compressor,
282 "lastPersistenceWrittenBytes",
283 nc_name,
284 nc_name_with_prefix,
285 nc_stats->get_last_persistence_written_bytes(),
286 metrics::probe_unit::BYTES);
287
288 add_near_cache_metric(stats,
289 compressor,
290 "misses",
291 nc_name,
292 nc_name_with_prefix,
293 nc_stats->get_misses(),
294 metrics::probe_unit::COUNT);
295
296 add_near_cache_metric(stats,
297 compressor,
298 "ownedEntryCount",
299 nc_name,
300 nc_name_with_prefix,
301 nc_stats->get_owned_entry_count(),
302 metrics::probe_unit::COUNT);
303
304 add_near_cache_metric(stats,
305 compressor,
306 "expirations",
307 nc_name,
308 nc_name_with_prefix,
309 nc_stats->get_expirations(),
310 metrics::probe_unit::COUNT);
311
312 add_near_cache_metric(stats,
313 compressor,
314 "invalidations",
315 nc_name,
316 nc_name_with_prefix,
317 nc_stats->get_invalidations(),
318 metrics::probe_unit::COUNT);
319
320 add_near_cache_metric(stats,
321 compressor,
322 "invalidationRequests",
323 nc_name,
324 nc_name_with_prefix,
325 nc_stats->get_invalidation_requests(),
326 metrics::probe_unit::COUNT);
327
328 add_near_cache_metric(stats,
329 compressor,
330 "ownedEntryMemoryCost",
331 nc_name,
332 nc_name_with_prefix,
333 nc_stats->get_owned_entry_memory_cost(),
334 metrics::probe_unit::BYTES);
335 }
336}
337
338void
339Statistics::PeriodicStatistics::add_near_cache_metric(
340 std::ostringstream& stats,
341 metrics::metrics_compressor& compressor,
342 const std::string& metric,
343 const std::string& near_cache_name,
344 const std::string& near_cache_name_with_prefix,
345 int64_t value,
346 metrics::probe_unit unit)
347{
348
349 metrics::metric_descriptor desc{
350 "nearcache", metric, "name", near_cache_name, unit
351 };
352 compressor.add_long(desc, value);
353
354 // necessary for compatibility with Management Center 4.0
355 add_stat(stats, near_cache_name_with_prefix, metric, value);
356}
357
358Statistics::PeriodicStatistics::PeriodicStatistics(Statistics& statistics)
359 : statistics_(statistics)
360{}
361
362std::string
363Statistics::escape_special_characters(std::string& name)
364{
365 boost::replace_all(name, ",", "\\,");
366 boost::replace_all(name, "=", "\\=");
367 boost::replace_all(name, "\\", "\\\\");
368
369 return name[0] == '/' ? name.substr(1) : name;
370}
371
372void
373Statistics::PeriodicStatistics::get_name_with_prefix(std::string& name,
374 std::ostringstream& out)
375{
376 out << NEAR_CACHE_CATEGORY_PREFIX
377 << Statistics::escape_special_characters(name);
378}
379
380template<>
381void
382Statistics::PeriodicStatistics::add_stat(std::ostringstream& stats,
383 const std::string& name,
384 const bool& value)
385{
386 stats << STAT_SEPARATOR << name << KEY_VALUE_SEPARATOR
387 << (value ? "true" : "false");
388}
389
390} // namespace statistics
391} // namespace impl
392
393namespace monitor {
394const int64_t local_instance_stats::STAT_NOT_AVAILABLE = -99L;
395
396namespace impl {
397LocalMapStatsImpl::LocalMapStatsImpl() = default;
398
399LocalMapStatsImpl::LocalMapStatsImpl(
400 const std::shared_ptr<monitor::near_cache_stats>& s)
401 : near_cache_stats_(s)
402{}
403
404std::shared_ptr<monitor::near_cache_stats>
405LocalMapStatsImpl::get_near_cache_stats() const
406{
407 return near_cache_stats_;
408}
409
410NearCacheStatsImpl::NearCacheStatsImpl()
411 : creation_time_(util::current_time_millis())
412 , owned_entry_count_(0)
413 , owned_entry_memory_cost_(0)
414 , hits_(0)
415 , misses_(0)
416 , evictions_(0)
417 , expirations_(0)
418 , invalidations_(0)
419 , invalidation_requests_(0)
420 , persistence_count_(0)
421 , last_persistence_time_(0)
422 , last_persistence_duration_(0)
423 , last_persistence_written_bytes_(0)
424 , last_persistence_key_count_(0)
425 , last_persistence_failure_("")
426{}
427
428int64_t
429NearCacheStatsImpl::get_creation_time()
430{
431 return creation_time_;
432}
433
434int64_t
435NearCacheStatsImpl::get_owned_entry_count()
436{
437 return owned_entry_count_;
438}
439
440void
441NearCacheStatsImpl::set_owned_entry_count(int64_t owned_entry_count)
442{
443 this->owned_entry_count_ = owned_entry_count;
444}
445
446void
447NearCacheStatsImpl::increment_owned_entry_count()
448{
449 ++owned_entry_count_;
450}
451
452void
453NearCacheStatsImpl::decrement_owned_entry_count()
454{
455 --owned_entry_count_;
456}
457
458int64_t
459NearCacheStatsImpl::get_owned_entry_memory_cost()
460{
461 return owned_entry_memory_cost_;
462}
463
464void
465NearCacheStatsImpl::set_owned_entry_memory_cost(int64_t owned_entry_memory_cost)
466{
467 this->owned_entry_memory_cost_ = owned_entry_memory_cost;
468}
469
470void
471NearCacheStatsImpl::increment_owned_entry_memory_cost(
472 int64_t owned_entry_memory_cost)
473{
474 this->owned_entry_memory_cost_ += owned_entry_memory_cost;
475}
476
477void
478NearCacheStatsImpl::decrement_owned_entry_memory_cost(
479 int64_t owned_entry_memory_cost)
480{
481 this->owned_entry_memory_cost_ -= owned_entry_memory_cost;
482}
483
484int64_t
485NearCacheStatsImpl::get_hits()
486{
487 return hits_;
488}
489
490// just for testing
491void
492NearCacheStatsImpl::set_hits(int64_t hits)
493{
494 this->hits_ = hits;
495}
496
497void
498NearCacheStatsImpl::increment_hits()
499{
500 ++hits_;
501}
502
503int64_t
504NearCacheStatsImpl::get_misses()
505{
506 return misses_;
507}
508
509// just for testing
510void
511NearCacheStatsImpl::set_misses(int64_t misses)
512{
513 this->misses_ = misses;
514}
515
516void
517NearCacheStatsImpl::increment_misses()
518{
519 ++misses_;
520}
521
522double
523NearCacheStatsImpl::get_ratio()
524{
525 if (misses_ == (int64_t)0) {
526 if (hits_ == (int64_t)0) {
527 return std::numeric_limits<double>::signaling_NaN();
528 } else {
529 return std::numeric_limits<double>::infinity();
530 }
531 } else {
532 return ((double)hits_ / misses_) * PERCENTAGE;
533 }
534}
535
536int64_t
537NearCacheStatsImpl::get_evictions()
538{
539 return evictions_;
540}
541
542void
543NearCacheStatsImpl::increment_evictions()
544{
545 ++evictions_;
546}
547
548int64_t
549NearCacheStatsImpl::get_expirations()
550{
551 return expirations_;
552}
553
554void
555NearCacheStatsImpl::increment_expirations()
556{
557 ++expirations_;
558}
559
560int64_t
561NearCacheStatsImpl::get_invalidations()
562{
563 return invalidations_.load();
564}
565
566void
567NearCacheStatsImpl::increment_invalidations()
568{
569 ++invalidations_;
570}
571
572int64_t
573NearCacheStatsImpl::get_invalidation_requests()
574{
575 return invalidation_requests_.load();
576}
577
578void
579NearCacheStatsImpl::increment_invalidation_requests()
580{
581 ++invalidation_requests_;
582}
583
584void
585NearCacheStatsImpl::reset_invalidation_events()
586{
587 invalidation_requests_ = 0;
588}
589
590int64_t
591NearCacheStatsImpl::get_persistence_count()
592{
593 return persistence_count_;
594}
595
596void
597NearCacheStatsImpl::add_persistence(int64_t duration,
598 int32_t written_bytes,
599 int32_t key_count)
600{
601 ++persistence_count_;
602 last_persistence_time_ = util::current_time_millis();
603 last_persistence_duration_ = duration;
604 last_persistence_written_bytes_ = written_bytes;
605 last_persistence_key_count_ = key_count;
606 last_persistence_failure_ = "";
607}
608
609int64_t
610NearCacheStatsImpl::get_last_persistence_time()
611{
612 return last_persistence_time_;
613}
614
615int64_t
616NearCacheStatsImpl::get_last_persistence_duration()
617{
618 return last_persistence_duration_;
619}
620
621int64_t
622NearCacheStatsImpl::get_last_persistence_written_bytes()
623{
624 return last_persistence_written_bytes_;
625}
626
627int64_t
628NearCacheStatsImpl::get_last_persistence_key_count()
629{
630 return last_persistence_key_count_;
631}
632
633std::string
634NearCacheStatsImpl::get_last_persistence_failure()
635{
636 return last_persistence_failure_;
637}
638
639std::string
640NearCacheStatsImpl::to_string()
641{
642 std::ostringstream out;
643 std::string failureString = last_persistence_failure_;
644 out << "NearCacheStatsImpl{"
645 << "ownedEntryCount=" << owned_entry_count_
646 << ", ownedEntryMemoryCost=" << owned_entry_memory_cost_
647 << ", creationTime=" << creation_time_ << ", hits=" << hits_
648 << ", misses=" << misses_ << ", ratio=" << std::setprecision(1)
649 << get_ratio() << ", evictions=" << evictions_
650 << ", expirations=" << expirations_
651 << ", invalidations=" << invalidations_.load()
652 << ", invalidationRequests=" << invalidation_requests_.load()
653 << ", lastPersistenceTime=" << last_persistence_time_
654 << ", persistenceCount=" << persistence_count_
655 << ", lastPersistenceDuration=" << last_persistence_duration_
656 << ", lastPersistenceWrittenBytes=" << last_persistence_written_bytes_
657 << ", lastPersistenceKeyCount=" << last_persistence_key_count_
658 << ", lastPersistenceFailure='" << failureString << "'" << '}';
659
660 return out.str();
661}
662
663const double NearCacheStatsImpl::PERCENTAGE = 100.0;
664
665} // namespace impl
666} // namespace monitor
667} // namespace client
668} // namespace hazelcast