Hazelcast C++ Client
Hazelcast C++ Client Library
stats.cpp
1 /*
2  * Copyright (c) 2008-2022, 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 
42 namespace hazelcast {
43 namespace client {
44 namespace impl {
45 namespace statistics {
46 const std::string Statistics::NEAR_CACHE_CATEGORY_PREFIX("nc.");
47 
48 Statistics::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 
58 void
59 Statistics::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 
95 void
96 Statistics::shutdown()
97 {
98  if (send_task_timer_) {
99  boost::system::error_code ignored;
100  send_task_timer_->cancel(ignored);
101  }
102 }
103 
104 void
105 Statistics::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 
144 std::shared_ptr<connection::Connection>
145 Statistics::get_connection()
146 {
147  return client_context_.get_connection_manager().get_random_connection();
148 }
149 
150 void
151 Statistics::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 
172 void
173 Statistics::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 
214 void
215 Statistics::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 
338 void
339 Statistics::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 
358 Statistics::PeriodicStatistics::PeriodicStatistics(Statistics& statistics)
359  : statistics_(statistics)
360 {}
361 
362 std::string
363 Statistics::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 
372 void
373 Statistics::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 
380 template<>
381 void
382 Statistics::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 
393 namespace monitor {
394 const int64_t local_instance_stats::STAT_NOT_AVAILABLE = -99L;
395 
396 namespace impl {
397 LocalMapStatsImpl::LocalMapStatsImpl() = default;
398 
399 LocalMapStatsImpl::LocalMapStatsImpl(
400  const std::shared_ptr<monitor::near_cache_stats>& s)
401  : near_cache_stats_(s)
402 {}
403 
404 std::shared_ptr<monitor::near_cache_stats>
405 LocalMapStatsImpl::get_near_cache_stats() const
406 {
407  return near_cache_stats_;
408 }
409 
410 NearCacheStatsImpl::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 
428 int64_t
429 NearCacheStatsImpl::get_creation_time()
430 {
431  return creation_time_;
432 }
433 
434 int64_t
435 NearCacheStatsImpl::get_owned_entry_count()
436 {
437  return owned_entry_count_;
438 }
439 
440 void
441 NearCacheStatsImpl::set_owned_entry_count(int64_t owned_entry_count)
442 {
443  this->owned_entry_count_ = owned_entry_count;
444 }
445 
446 void
447 NearCacheStatsImpl::increment_owned_entry_count()
448 {
449  ++owned_entry_count_;
450 }
451 
452 void
453 NearCacheStatsImpl::decrement_owned_entry_count()
454 {
455  --owned_entry_count_;
456 }
457 
458 int64_t
459 NearCacheStatsImpl::get_owned_entry_memory_cost()
460 {
461  return owned_entry_memory_cost_;
462 }
463 
464 void
465 NearCacheStatsImpl::set_owned_entry_memory_cost(int64_t owned_entry_memory_cost)
466 {
467  this->owned_entry_memory_cost_ = owned_entry_memory_cost;
468 }
469 
470 void
471 NearCacheStatsImpl::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 
477 void
478 NearCacheStatsImpl::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 
484 int64_t
485 NearCacheStatsImpl::get_hits()
486 {
487  return hits_;
488 }
489 
490 // just for testing
491 void
492 NearCacheStatsImpl::set_hits(int64_t hits)
493 {
494  this->hits_ = hits;
495 }
496 
497 void
498 NearCacheStatsImpl::increment_hits()
499 {
500  ++hits_;
501 }
502 
503 int64_t
504 NearCacheStatsImpl::get_misses()
505 {
506  return misses_;
507 }
508 
509 // just for testing
510 void
511 NearCacheStatsImpl::set_misses(int64_t misses)
512 {
513  this->misses_ = misses;
514 }
515 
516 void
517 NearCacheStatsImpl::increment_misses()
518 {
519  ++misses_;
520 }
521 
522 double
523 NearCacheStatsImpl::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 
536 int64_t
537 NearCacheStatsImpl::get_evictions()
538 {
539  return evictions_;
540 }
541 
542 void
543 NearCacheStatsImpl::increment_evictions()
544 {
545  ++evictions_;
546 }
547 
548 int64_t
549 NearCacheStatsImpl::get_expirations()
550 {
551  return expirations_;
552 }
553 
554 void
555 NearCacheStatsImpl::increment_expirations()
556 {
557  ++expirations_;
558 }
559 
560 int64_t
561 NearCacheStatsImpl::get_invalidations()
562 {
563  return invalidations_.load();
564 }
565 
566 void
567 NearCacheStatsImpl::increment_invalidations()
568 {
569  ++invalidations_;
570 }
571 
572 int64_t
573 NearCacheStatsImpl::get_invalidation_requests()
574 {
575  return invalidation_requests_.load();
576 }
577 
578 void
579 NearCacheStatsImpl::increment_invalidation_requests()
580 {
581  ++invalidation_requests_;
582 }
583 
584 void
585 NearCacheStatsImpl::reset_invalidation_events()
586 {
587  invalidation_requests_ = 0;
588 }
589 
590 int64_t
591 NearCacheStatsImpl::get_persistence_count()
592 {
593  return persistence_count_;
594 }
595 
596 void
597 NearCacheStatsImpl::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 
609 int64_t
610 NearCacheStatsImpl::get_last_persistence_time()
611 {
612  return last_persistence_time_;
613 }
614 
615 int64_t
616 NearCacheStatsImpl::get_last_persistence_duration()
617 {
618  return last_persistence_duration_;
619 }
620 
621 int64_t
622 NearCacheStatsImpl::get_last_persistence_written_bytes()
623 {
624  return last_persistence_written_bytes_;
625 }
626 
627 int64_t
628 NearCacheStatsImpl::get_last_persistence_key_count()
629 {
630  return last_persistence_key_count_;
631 }
632 
633 std::string
634 NearCacheStatsImpl::get_last_persistence_failure()
635 {
636  return last_persistence_failure_;
637 }
638 
639 std::string
640 NearCacheStatsImpl::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 
663 const double NearCacheStatsImpl::PERCENTAGE = 100.0;
664 
665 } // namespace impl
666 } // namespace monitor
667 } // namespace client
668 } // namespace hazelcast