1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2016 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 #include "engine_errc_2_mcbp.h"
18 #include "engine_wrapper.h"
19 #include "logger/logger.h"
20 #include "stats_context.h"
21 #include "utilities.h"
22 
23 #include <daemon/buckets.h>
24 #include <daemon/connection.h>
25 #include <daemon/cookie.h>
26 #include <daemon/executorpool.h>
27 #include <daemon/mc_time.h>
28 #include <daemon/mcaudit.h>
29 #include <daemon/memcached.h>
30 #include <daemon/runtime.h>
31 #include <daemon/settings.h>
32 #include <daemon/stats.h>
33 #include <daemon/stats_tasks.h>
34 #include <daemon/topkeys.h>
35 #include <mcbp/protocol/framebuilder.h>
36 #include <mcbp/protocol/header.h>
37 #include <memcached/audit_interface.h>
38 #include <nlohmann/json.hpp>
39 #include <phosphor/stats_callback.h>
40 #include <phosphor/trace_log.h>
41 #include <platform/checked_snprintf.h>
42 
43 #include <gsl/gsl>
44 
45 /*************************** ADD STAT CALLBACKS ***************************/
46 
47 // Generic add_stat<T>. Uses std::to_string which requires heap allocation.
48 template <typename T>
add_stat(Cookie& cookie, const AddStatFn& add_stat_callback, const char* name, const T& val)49 void add_stat(Cookie& cookie,
50               const AddStatFn& add_stat_callback,
51               const char* name,
52               const T& val) {
53     std::string value = std::to_string(val);
54     add_stat_callback(name,
55                       uint16_t(strlen(name)),
56                       value.c_str(),
57                       uint32_t(value.length()),
58                       &cookie);
59 }
60 
61 // Specializations for common, integer types. Uses stack buffer for
62 // int-to-string conversion.
add_stat(Cookie& cookie, const AddStatFn& add_stat_callback, const char* name, int32_t val)63 void add_stat(Cookie& cookie,
64               const AddStatFn& add_stat_callback,
65               const char* name,
66               int32_t val) {
67     char buf[16];
68     int len = checked_snprintf(buf, sizeof(buf), "%" PRId32, val);
69     if (len < 0 || size_t(len) >= sizeof(buf)) {
70         LOG_WARNING("add_stat failed to add stat for {}", name);
71     } else {
72         add_stat_callback(
73                 name, uint16_t(strlen(name)), buf, uint32_t(len), &cookie);
74     }
75 }
76 
add_stat(Cookie& cookie, const AddStatFn& add_stat_callback, const char* name, uint32_t val)77 void add_stat(Cookie& cookie,
78               const AddStatFn& add_stat_callback,
79               const char* name,
80               uint32_t val) {
81     char buf[16];
82     int len = checked_snprintf(buf, sizeof(buf), "%" PRIu32, val);
83     if (len < 0 || size_t(len) >= sizeof(buf)) {
84         LOG_WARNING("add_stat failed to add stat for {}", name);
85     } else {
86         add_stat_callback(
87                 name, uint16_t(strlen(name)), buf, uint32_t(len), &cookie);
88     }
89 }
90 
add_stat(Cookie& cookie, const AddStatFn& add_stat_callback, const char* name, int64_t val)91 void add_stat(Cookie& cookie,
92               const AddStatFn& add_stat_callback,
93               const char* name,
94               int64_t val) {
95     char buf[32];
96     int len = checked_snprintf(buf, sizeof(buf), "%" PRId64, val);
97     if (len < 0 || size_t(len) >= sizeof(buf)) {
98         LOG_WARNING("add_stat failed to add stat for {}", name);
99     } else {
100         add_stat_callback(
101                 name, uint16_t(strlen(name)), buf, uint32_t(len), &cookie);
102     }
103 }
104 
add_stat(Cookie& cookie, const AddStatFn& add_stat_callback, const char* name, uint64_t val)105 void add_stat(Cookie& cookie,
106               const AddStatFn& add_stat_callback,
107               const char* name,
108               uint64_t val) {
109     char buf[32];
110     int len = checked_snprintf(buf, sizeof(buf), "%" PRIu64, val);
111     if (len < 0 || size_t(len) >= sizeof(buf)) {
112         LOG_WARNING("add_stat failed to add stat for {}", name);
113     } else {
114         add_stat_callback(
115                 name, uint16_t(strlen(name)), buf, uint32_t(len), &cookie);
116     }
117 }
118 
add_stat(Cookie& cookie, const AddStatFn& add_stat_callback, const char* name, const std::string& value)119 void add_stat(Cookie& cookie,
120               const AddStatFn& add_stat_callback,
121               const char* name,
122               const std::string& value) {
123     add_stat_callback(name,
124                       uint16_t(strlen(name)),
125                       value.c_str(),
126                       uint32_t(value.length()),
127                       &cookie);
128 }
129 
add_stat(Cookie& cookie, const AddStatFn& add_stat_callback, const char* name, const char* value)130 void add_stat(Cookie& cookie,
131               const AddStatFn& add_stat_callback,
132               const char* name,
133               const char* value) {
134     add_stat_callback(name,
135                       uint16_t(strlen(name)),
136                       value,
137                       uint32_t(strlen(value)),
138                       &cookie);
139 }
140 
add_stat(Cookie& cookie, const AddStatFn& add_stat_callback, const char* name, const bool value)141 void add_stat(Cookie& cookie,
142               const AddStatFn& add_stat_callback,
143               const char* name,
144               const bool value) {
145     if (value) {
146         add_stat(cookie, add_stat_callback, name, "true");
147     } else {
148         add_stat(cookie, add_stat_callback, name, "false");
149     }
150 }
151 
152 /* return server specific stats only */
server_stats(const AddStatFn& add_stat_callback, Cookie& cookie)153 static ENGINE_ERROR_CODE server_stats(const AddStatFn& add_stat_callback,
154                                       Cookie& cookie) {
155     rel_time_t now = mc_time_get_current_time();
156 
157     struct thread_stats thread_stats;
158     thread_stats.aggregate(cookie.getConnection().getBucket().stats);
159 
160     try {
161         std::lock_guard<std::mutex> guard(stats_mutex);
162 
163         add_stat(cookie, add_stat_callback, "uptime", now);
164         add_stat(cookie, add_stat_callback, "stat_reset",
165                  (const char*)reset_stats_time);
166         add_stat(cookie, add_stat_callback, "time",
167                  mc_time_convert_to_abs_time(now));
168         add_stat(cookie, add_stat_callback, "version", get_server_version());
169         add_stat(cookie, add_stat_callback, "memcached_version", MEMCACHED_VERSION);
170         add_stat(cookie, add_stat_callback, "libevent", event_get_version());
171         add_stat(cookie, add_stat_callback, "pointer_size", (8 * sizeof(void*)));
172 
173         add_stat(cookie, add_stat_callback, "daemon_connections",
174                  stats.daemon_conns);
175         add_stat(cookie, add_stat_callback, "curr_connections",
176                  stats.curr_conns.load(std::memory_order_relaxed));
177         add_stat(cookie,
178                  add_stat_callback,
179                  "system_connections",
180                  stats.system_conns.load(std::memory_order_relaxed));
181         add_stat(cookie, add_stat_callback, "total_connections", stats.total_conns);
182         add_stat(cookie, add_stat_callback, "connection_structures",
183                  stats.conn_structs);
184         add_stat(cookie, add_stat_callback, "cmd_get", thread_stats.cmd_get);
185         add_stat(cookie, add_stat_callback, "cmd_set", thread_stats.cmd_set);
186         add_stat(cookie, add_stat_callback, "cmd_flush", thread_stats.cmd_flush);
187 
188         add_stat(cookie, add_stat_callback, "cmd_subdoc_lookup",
189                  thread_stats.cmd_subdoc_lookup);
190         add_stat(cookie, add_stat_callback, "cmd_subdoc_mutation",
191                  thread_stats.cmd_subdoc_mutation);
192 
193         add_stat(cookie, add_stat_callback, "bytes_subdoc_lookup_total",
194                  thread_stats.bytes_subdoc_lookup_total);
195         add_stat(cookie, add_stat_callback, "bytes_subdoc_lookup_extracted",
196                  thread_stats.bytes_subdoc_lookup_extracted);
197         add_stat(cookie, add_stat_callback, "bytes_subdoc_mutation_total",
198                  thread_stats.bytes_subdoc_mutation_total);
199         add_stat(cookie, add_stat_callback, "bytes_subdoc_mutation_inserted",
200                  thread_stats.bytes_subdoc_mutation_inserted);
201 
202         // index 0 contains the aggregated timings for all buckets
203         auto& timings = all_buckets[0].timings;
204         uint64_t total_mutations = timings.get_aggregated_mutation_stats();
205         uint64_t total_retrivals = timings.get_aggregated_retrival_stats();
206         uint64_t total_ops = total_retrivals + total_mutations;
207         add_stat(cookie, add_stat_callback, "cmd_total_sets", total_mutations);
208         add_stat(cookie, add_stat_callback, "cmd_total_gets", total_retrivals);
209         add_stat(cookie, add_stat_callback, "cmd_total_ops", total_ops);
210 
211         // bucket specific totals
212         auto& current_bucket_timings =
213                 cookie.getConnection().getBucket().timings;
214         uint64_t mutations = current_bucket_timings.get_aggregated_mutation_stats();
215         uint64_t lookups = current_bucket_timings.get_aggregated_retrival_stats();
216         add_stat(cookie, add_stat_callback, "cmd_mutation", mutations);
217         add_stat(cookie, add_stat_callback, "cmd_lookup", lookups);
218 
219         add_stat(cookie, add_stat_callback, "auth_cmds", thread_stats.auth_cmds);
220         add_stat(cookie, add_stat_callback, "auth_errors", thread_stats.auth_errors);
221         add_stat(cookie, add_stat_callback, "get_hits", thread_stats.get_hits);
222         add_stat(cookie, add_stat_callback, "get_misses", thread_stats.get_misses);
223         add_stat(cookie, add_stat_callback, "delete_misses",
224                  thread_stats.delete_misses);
225         add_stat(cookie, add_stat_callback, "delete_hits", thread_stats.delete_hits);
226         add_stat(cookie, add_stat_callback, "incr_misses", thread_stats.incr_misses);
227         add_stat(cookie, add_stat_callback, "incr_hits", thread_stats.incr_hits);
228         add_stat(cookie, add_stat_callback, "decr_misses", thread_stats.decr_misses);
229         add_stat(cookie, add_stat_callback, "decr_hits", thread_stats.decr_hits);
230         add_stat(cookie, add_stat_callback, "cas_misses", thread_stats.cas_misses);
231         add_stat(cookie, add_stat_callback, "cas_hits", thread_stats.cas_hits);
232         add_stat(cookie, add_stat_callback, "cas_badval", thread_stats.cas_badval);
233         add_stat(cookie, add_stat_callback, "bytes_read", thread_stats.bytes_read);
234         add_stat(cookie, add_stat_callback, "bytes_written",
235                  thread_stats.bytes_written);
236         add_stat(cookie, add_stat_callback, "accepting_conns",
237                  is_listen_disabled() ? 0 : 1);
238         add_stat(cookie, add_stat_callback, "listen_disabled_num",
239                  get_listen_disabled_num());
240         add_stat(cookie, add_stat_callback, "rejected_conns", stats.rejected_conns);
241         add_stat(cookie,
242                  add_stat_callback,
243                  "threads",
244                  Settings::instance().getNumWorkerThreads());
245         add_stat(cookie, add_stat_callback, "conn_yields", thread_stats.conn_yields);
246         add_stat(cookie, add_stat_callback, "rbufs_allocated",
247                  thread_stats.rbufs_allocated);
248         add_stat(cookie, add_stat_callback, "rbufs_loaned",
249                  thread_stats.rbufs_loaned);
250         add_stat(cookie, add_stat_callback, "rbufs_existing",
251                  thread_stats.rbufs_existing);
252         add_stat(cookie, add_stat_callback, "wbufs_allocated",
253                  thread_stats.wbufs_allocated);
254         add_stat(cookie, add_stat_callback, "wbufs_loaned",
255                  thread_stats.wbufs_loaned);
256         add_stat(cookie,
257                  add_stat_callback,
258                  "wbufs_existing",
259                  thread_stats.wbufs_existing);
260         add_stat(cookie, add_stat_callback, "iovused_high_watermark",
261                  thread_stats.iovused_high_watermark);
262         add_stat(cookie, add_stat_callback, "msgused_high_watermark",
263                  thread_stats.msgused_high_watermark);
264 
265         add_stat(cookie, add_stat_callback, "cmd_lock", thread_stats.cmd_lock);
266         add_stat(cookie, add_stat_callback, "lock_errors",
267                  thread_stats.lock_errors);
268 
269         auto lookup_latency = timings.get_interval_lookup_latency();
270         add_stat(cookie, add_stat_callback, "cmd_lookup_10s_count",
271                  lookup_latency.count);
272         add_stat(cookie, add_stat_callback, "cmd_lookup_10s_duration_us",
273                  lookup_latency.duration_ns / 1000);
274 
275         auto mutation_latency = timings.get_interval_mutation_latency();
276         add_stat(cookie, add_stat_callback, "cmd_mutation_10s_count",
277                  mutation_latency.count);
278         add_stat(cookie, add_stat_callback, "cmd_mutation_10s_duration_us",
279                  mutation_latency.duration_ns / 1000);
280 
281         auto& respCounters =
282                 cookie.getConnection().getBucket().responseCounters;
283         // Ignore success responses by starting from begin + 1
284         uint64_t total_resp_errors = std::accumulate(
285                 std::begin(respCounters) + 1, std::end(respCounters), 0);
286         add_stat(cookie,
287                  add_stat_callback,
288                  "total_resp_errors",
289                  total_resp_errors);
290 
291     } catch (const std::bad_alloc&) {
292         return ENGINE_ENOMEM;
293     }
294 
295     return ENGINE_SUCCESS;
296 }
297 
append_bin_stats(const char* key, const uint16_t klen, const char* val, const uint32_t vlen, Cookie& cookie)298 static void append_bin_stats(const char* key,
299                              const uint16_t klen,
300                              const char* val,
301                              const uint32_t vlen,
302                              Cookie& cookie) {
303     auto& dbuf = cookie.getDynamicBuffer();
304     // We've ensured that there is enough room in the buffer before calling
305     // this method
306     auto* buf = reinterpret_cast<uint8_t*>(dbuf.getCurrent());
307     cb::mcbp::ResponseBuilder builder(
308             cb::byte_buffer(buf, dbuf.getSize() - dbuf.getOffset()));
309     builder.setMagic(cb::mcbp::Magic::ClientResponse);
310     builder.setOpcode(cb::mcbp::ClientOpcode::Stat);
311     builder.setDatatype(cb::mcbp::Datatype::Raw);
312     builder.setStatus(cb::mcbp::Status::Success);
313     builder.setKey(
314             cb::const_byte_buffer(reinterpret_cast<const uint8_t*>(key), klen));
315     builder.setValue(
316             cb::const_byte_buffer(reinterpret_cast<const uint8_t*>(val), vlen));
317     builder.setOpaque(cookie.getHeader().getOpaque());
318     builder.validate();
319     dbuf.moveOffset(sizeof(cb::mcbp::Response) +
320                     builder.getFrame()->getBodylen());
321 }
322 
append_stats(const char* key, const uint16_t klen, const char* val, const uint32_t vlen, gsl::not_null<const void*> void_cookie)323 static void append_stats(const char* key,
324                          const uint16_t klen,
325                          const char* val,
326                          const uint32_t vlen,
327                          gsl::not_null<const void*> void_cookie) {
328     size_t needed;
329 
330     auto& cookie = *const_cast<Cookie*>(
331             reinterpret_cast<const Cookie*>(void_cookie.get()));
332     needed = vlen + klen + sizeof(protocol_binary_response_header);
333     if (!cookie.growDynamicBuffer(needed)) {
334         return;
335     }
336     append_bin_stats(key, klen, val, vlen, cookie);
337 }
338 
339 // Create a static std::function to wrap append_stats, instead of creating a
340 // temporary object every time we need to call into an engine.
341 // This also avoids problems where the stack-allocated AddStatFn could go
342 // out of scope if someone needs to take a copy of it and run it on another
343 // thread.
344 static AddStatFn appendStatsFn = append_stats;
345 
346 /**
347  * This is a very slow thing that you shouldn't use in production ;-)
348  *
349  * @param c the connection to return the details for
350  */
process_bucket_details(Cookie& cookie)351 static void process_bucket_details(Cookie& cookie) {
352     nlohmann::json json;
353     nlohmann::json array = nlohmann::json::array();
354 
355     for (size_t ii = 0; ii < all_buckets.size(); ++ii) {
356         auto o = get_bucket_details(ii);
357         if (o.size() > 0) {
358             array.push_back(o);
359         }
360     }
361     json["buckets"] = array;
362 
363     const auto stats_str = json.dump();
364     append_stats("bucket details",
365                  14,
366                  stats_str.data(),
367                  uint32_t(stats_str.size()),
368                  static_cast<void*>(&cookie));
369 }
370 
371 /**
372  * Handler for the <code>stats reset</code> command.
373  *
374  * Clear the global and the connected buckets stats.
375  *
376  * It is possible to clear a subset of the stats by using its submodule.
377  * The following submodules exists:
378  * <ul>
379  *    <li>timings</li>
380  * </ul>
381  *
382  * @todo I would have assumed that we wanted to clear the stats from
383  *       <b>all</b> of the buckets?
384  *
385  * @param arg - should be empty
386  * @param cookie the command context
387  */
stat_reset_executor(const std::string& arg, Cookie& cookie)388 static ENGINE_ERROR_CODE stat_reset_executor(const std::string& arg,
389                                              Cookie& cookie) {
390     if (arg.empty()) {
391         stats_reset(cookie);
392         bucket_reset_stats(cookie);
393         all_buckets[0].timings.reset();
394         all_buckets[cookie.getConnection().getBucketIndex()].timings.reset();
395         return ENGINE_SUCCESS;
396     } else if (arg == "timings") {
397         // Nuke the command timings section for the connected bucket
398         all_buckets[cookie.getConnection().getBucketIndex()].timings.reset();
399         return ENGINE_SUCCESS;
400     } else {
401         return ENGINE_EINVAL;
402     }
403 }
404 
405 /**
406  * Handler for the <code>stats sched</code> used to get the
407  * histogram for the scheduler histogram.
408  *
409  * @param arg - should be empty
410  * @param cookie the command context
411  */
stat_sched_executor(const std::string& arg, Cookie& cookie)412 static ENGINE_ERROR_CODE stat_sched_executor(const std::string& arg,
413                                              Cookie& cookie) {
414     if (arg.empty()) {
415         for (size_t ii = 0; ii < Settings::instance().getNumWorkerThreads();
416              ++ii) {
417             auto hist = scheduler_info[ii].to_string();
418             std::string key = std::to_string(ii);
419             append_stats(key.data(),
420                          gsl::narrow<uint16_t>(key.size()),
421                          hist.data(),
422                          gsl::narrow<uint32_t>(hist.size()),
423                          &cookie);
424         }
425         return ENGINE_SUCCESS;
426     } else if (arg == "aggregate") {
427         static const std::string key = {"aggregate"};
428         Hdr1sfMicroSecHistogram histogram{};
429         for (const auto& h : scheduler_info) {
430             histogram += h;
431         }
432         // Add the stat
433         auto hist = histogram.to_string();
434         append_stats(key.data(),
435                      gsl::narrow<uint16_t>(key.size()),
436                      hist.data(),
437                      gsl::narrow<uint32_t>(hist.size()),
438                      &cookie);
439         return ENGINE_SUCCESS;
440     } else {
441         return ENGINE_EINVAL;
442     }
443 }
444 
445 /**
446  * Handler for the <code>stats audit</code> used to get statistics from
447  * the audit subsystem.
448  *
449  * @param arg - should be empty
450  * @param cookie the command context
451  */
stat_audit_executor(const std::string& arg, Cookie& cookie)452 static ENGINE_ERROR_CODE stat_audit_executor(const std::string& arg,
453                                              Cookie& cookie) {
454     if (arg.empty()) {
455         stats_audit(appendStatsFn, cookie);
456         return ENGINE_SUCCESS;
457     } else {
458         return ENGINE_EINVAL;
459     }
460 }
461 
462 /**
463  * Handler for the <code>stats bucket details</code> used to get information
464  * of the buckets (type, state, #clients etc)
465  *
466  * @param arg - should be empty
467  * @param cookie the command context
468  */
stat_bucket_details_executor(const std::string& arg, Cookie& cookie)469 static ENGINE_ERROR_CODE stat_bucket_details_executor(const std::string& arg,
470                                                       Cookie& cookie) {
471     if (arg.empty()) {
472         process_bucket_details(cookie);
473         return ENGINE_SUCCESS;
474     } else {
475         return ENGINE_EINVAL;
476     }
477 }
478 
479 /**
480  * Handler for the <code>stats aggregate</code>.. probably not used anymore
481  * as it gives just a subset of what you'll get from an empty stat.
482  *
483  * @param arg - should be empty
484  * @param cookie the command context
485  */
stat_aggregate_executor(const std::string& arg, Cookie& cookie)486 static ENGINE_ERROR_CODE stat_aggregate_executor(const std::string& arg,
487                                                  Cookie& cookie) {
488     if (arg.empty()) {
489         return server_stats(appendStatsFn, cookie);
490     } else {
491         return ENGINE_EINVAL;
492     }
493 }
494 
495 /**
496  * Handler for the <code>stats connection[ fd]</code> command to retrieve
497  * information about connection specific details.
498  *
499  * @param arg an optional file descriptor representing the connection
500  *            object to retrieve information about. If empty dump all.
501  * @param cookie the command context
502  */
stat_connections_executor(const std::string& arg, Cookie& cookie)503 static ENGINE_ERROR_CODE stat_connections_executor(const std::string& arg,
504                                                    Cookie& cookie) {
505     int64_t fd = -1;
506 
507     if (!arg.empty()) {
508         try {
509             fd = std::stoll(arg);
510         } catch (...) {
511             return ENGINE_EINVAL;
512         }
513     }
514 
515     std::shared_ptr<Task> task = std::make_shared<StatsTaskConnectionStats>(
516             cookie.getConnection(), cookie, appendStatsFn, fd);
517     cookie.obtainContext<StatsCommandContext>(cookie).setTask(task);
518     std::lock_guard<std::mutex> guard(task->getMutex());
519     executorPool->schedule(task, true);
520 
521     return ENGINE_EWOULDBLOCK;
522 }
523 
524 /**
525  * Handler for the <code>stats topkeys</code> command used to retrieve
526  * the most popular keys in the attached bucket.
527  *
528  * @param arg - should be empty
529  * @param cookie the command context
530  */
stat_topkeys_executor(const std::string& arg, Cookie& cookie)531 static ENGINE_ERROR_CODE stat_topkeys_executor(const std::string& arg,
532                                                Cookie& cookie) {
533     if (arg.empty()) {
534         auto& bucket = all_buckets[cookie.getConnection().getBucketIndex()];
535         if (bucket.topkeys == nullptr) {
536             return ENGINE_NO_BUCKET;
537         }
538         return bucket.topkeys->stats(
539                 &cookie, mc_time_get_current_time(), appendStatsFn);
540     } else {
541         return ENGINE_EINVAL;
542     }
543 }
544 
545 /**
546  * Handler for the <code>stats topkeys</code> command used to retrieve
547  * a JSON document containing the most popular keys in the attached bucket.
548  *
549  * @param arg - should be empty
550  * @param cookie the command context
551  */
stat_topkeys_json_executor(const std::string& arg, Cookie& cookie)552 static ENGINE_ERROR_CODE stat_topkeys_json_executor(const std::string& arg,
553                                                     Cookie& cookie) {
554     if (arg.empty()) {
555         ENGINE_ERROR_CODE ret;
556 
557         nlohmann::json topkeys_doc;
558 
559         auto& bucket = all_buckets[cookie.getConnection().getBucketIndex()];
560         if (bucket.topkeys == nullptr) {
561             return ENGINE_NO_BUCKET;
562         }
563         ret = bucket.topkeys->json_stats(topkeys_doc,
564                                          mc_time_get_current_time());
565 
566         if (ret == ENGINE_SUCCESS) {
567             char key[] = "topkeys_json";
568             const auto topkeys_str = topkeys_doc.dump();
569             append_stats(key,
570                          (uint16_t)strlen(key),
571                          topkeys_str.data(),
572                          uint32_t(topkeys_str.size()),
573                          &cookie);
574         }
575         return ret;
576     } else {
577         return ENGINE_EINVAL;
578     }
579 }
580 
581 /**
582  * Handler for the <code>stats subdoc_execute</code> command used to retrieve
583  * information from the subdoc subsystem.
584  *
585  * @param arg - should be empty
586  * @param cookie the command context
587  */
stat_subdoc_execute_executor(const std::string& arg, Cookie& cookie)588 static ENGINE_ERROR_CODE stat_subdoc_execute_executor(const std::string& arg,
589                                                       Cookie& cookie) {
590     if (arg.empty()) {
591         const auto index = cookie.getConnection().getBucketIndex();
592         std::string json_str;
593         if (index == 0) {
594             // Aggregrated timings for all buckets.
595             Hdr1sfMicroSecHistogram aggregated{};
596             for (const auto& bucket : all_buckets) {
597                 aggregated += bucket.subjson_operation_times;
598             }
599             json_str = aggregated.to_string();
600         } else {
601             // Timings for a specific bucket.
602             auto& bucket = all_buckets[cookie.getConnection().getBucketIndex()];
603             json_str = bucket.subjson_operation_times.to_string();
604         }
605         append_stats(nullptr,
606                      0,
607                      json_str.c_str(),
608                      gsl::narrow<uint32_t>(json_str.size()),
609                      &cookie);
610         return ENGINE_SUCCESS;
611     } else {
612         return ENGINE_EINVAL;
613     }
614 }
615 
stat_responses_json_executor(const std::string& arg, Cookie& cookie)616 static ENGINE_ERROR_CODE stat_responses_json_executor(const std::string& arg,
617                                                       Cookie& cookie) {
618     try {
619         auto& respCounters =
620                 cookie.getConnection().getBucket().responseCounters;
621         nlohmann::json json;
622 
623         for (uint16_t resp = 0; resp < respCounters.size(); ++resp) {
624             const auto value = respCounters[resp].load();
625             if (value > 0) {
626                 std::stringstream stream;
627                 stream << std::hex << resp;
628                 json[stream.str()] = value;
629             }
630         }
631 
632         std::string json_str = json.dump();
633         const std::string stat_name = "responses";
634         append_stats(stat_name.c_str(),
635                      gsl::narrow<uint16_t>(stat_name.size()),
636                      json_str.c_str(),
637                      gsl::narrow<uint32_t>(json_str.size()),
638                      &cookie);
639         return ENGINE_SUCCESS;
640     } catch (const std::bad_alloc&) {
641         return ENGINE_ENOMEM;
642     }
643 }
644 
stat_tracing_executor(const std::string& arg, Cookie& cookie)645 static ENGINE_ERROR_CODE stat_tracing_executor(const std::string& arg,
646                                                Cookie& cookie) {
647     class MemcachedCallback : public phosphor::StatsCallback {
648     public:
649         explicit MemcachedCallback(Cookie& cookie) : c(cookie) {
650         }
651 
652         void operator()(gsl_p::cstring_span key,
653                         gsl_p::cstring_span value) override {
654             append_stats(key.data(),
655                          gsl::narrow<uint16_t>(key.size()),
656                          value.data(),
657                          gsl::narrow<uint32_t>(value.size()),
658                          &c);
659         }
660 
661         void operator()(gsl_p::cstring_span key, bool value) override {
662             const auto svalue = value ? "true"_ccb : "false"_ccb;
663             append_stats(key.data(),
664                          gsl::narrow<uint16_t>(key.size()),
665                          svalue.data(),
666                          gsl::narrow<uint32_t>(svalue.size()),
667                          &c);
668         }
669 
670         void operator()(gsl_p::cstring_span key, size_t value) override {
671             const auto svalue = std::to_string(value);
672             append_stats(key.data(),
673                          gsl::narrow<uint16_t>(key.size()),
674                          svalue.data(),
675                          gsl::narrow<uint32_t>(svalue.size()),
676                          &c);
677         }
678 
679         void operator()(gsl_p::cstring_span key,
680                         phosphor::ssize_t value) override {
681             const auto svalue = std::to_string(value);
682             append_stats(key.data(),
683                          gsl::narrow<uint16_t>(key.size()),
684                          svalue.data(),
685                          gsl::narrow<uint32_t>(svalue.size()),
686                          &c);
687         }
688 
689         void operator()(gsl_p::cstring_span key, double value) override {
690             const auto svalue = std::to_string(value);
691             append_stats(key.data(),
692                          gsl::narrow<uint16_t>(key.size()),
693                          svalue.data(),
694                          gsl::narrow<uint32_t>(svalue.size()),
695                          &c);
696         }
697 
698     private:
699         Cookie& c;
700     };
701 
702     if (arg.empty()) {
703         MemcachedCallback cb{cookie};
704         phosphor::TraceLog::getInstance().getStats(cb);
705         return ENGINE_SUCCESS;
706     } else {
707         return ENGINE_EINVAL;
708     }
709 }
710 
stat_all_stats(const std::string& arg, Cookie& cookie)711 static ENGINE_ERROR_CODE stat_all_stats(const std::string& arg,
712                                         Cookie& cookie) {
713     auto value = cookie.getRequest().getValue();
714     auto ret = bucket_get_stats(cookie, arg, value, appendStatsFn);
715     if (ret == ENGINE_SUCCESS) {
716         ret = server_stats(appendStatsFn, cookie);
717     }
718     return ret;
719 }
720 
stat_bucket_stats(const std::string& arg, Cookie& cookie)721 static ENGINE_ERROR_CODE stat_bucket_stats(const std::string& arg,
722                                            Cookie& cookie) {
723     auto value = cookie.getRequest().getValue();
724     return bucket_get_stats(cookie, arg, value, appendStatsFn);
725 }
726 
727 /***************************** STAT HANDLERS *****************************/
728 
729 struct command_stat_handler {
730     /**
731      * Is this a privileged stat or may it be requested by anyone
732      */
733     bool privileged;
734 
735     /**
736      * The callback function to handle the stat request
737      */
738     ENGINE_ERROR_CODE (*handler)(const std::string& arg, Cookie& cookie);
739 };
740 
741 /**
742  * A mapping from all stat subgroups to the callback providing the
743  * statistics
744  */
745 static std::unordered_map<std::string, struct command_stat_handler>
746         stat_handlers = {
747                 {"", {false, stat_all_stats}},
748                 {"reset", {true, stat_reset_executor}},
749                 {"worker_thread_info", {false, stat_sched_executor}},
750                 {"audit", {true, stat_audit_executor}},
751                 {"bucket_details", {true, stat_bucket_details_executor}},
752                 {"aggregate", {false, stat_aggregate_executor}},
753                 {"connections", {true, stat_connections_executor}},
754                 {"topkeys", {false, stat_topkeys_executor}},
755                 {"topkeys_json", {false, stat_topkeys_json_executor}},
756                 {"subdoc_execute", {false, stat_subdoc_execute_executor}},
757                 {"responses", {false, stat_responses_json_executor}},
758                 {"tracing", {true, stat_tracing_executor}}};
759 
760 /**
761  * For a given key, try and return the handler for it
762  * @param key The key we wish to handle
763  * @return std::pair<stat_handler, bool> returns the stat handler and a bool
764  * representing whether this is a key we recognise or not
765  */
getStatHandler( const std::string& key)766 static std::pair<command_stat_handler, bool> getStatHandler(
767         const std::string& key) {
768     auto iter = stat_handlers.find(key);
769     if (iter == stat_handlers.end()) {
770         return std::make_pair(command_stat_handler{false, stat_bucket_stats}, false);
771     } else {
772         return std::make_pair(iter->second, true);
773     }
774 }
775 
776 /************************* STATE MACHINE EXECUTION *************************/
777 
step()778 ENGINE_ERROR_CODE StatsCommandContext::step() {
779     auto ret = ENGINE_SUCCESS;
780     do {
781         switch (state) {
782         case State::ParseCommandKey:
783             ret = parseCommandKey();
784             break;
785         case State::CheckPrivilege:
786             ret = checkPrivilege();
787             break;
788         case State::DoStats:
789             ret = doStats();
790             break;
791         case State::GetTaskResult:
792             ret = getTaskResult();
793             break;
794         case State::CommandComplete:
795             ret = commandComplete();
796             break;
797         case State::Done:
798             return command_exit_code;
799         }
800     } while (ret == ENGINE_SUCCESS);
801 
802     return ret;
803 }
804 
parseCommandKey()805 ENGINE_ERROR_CODE StatsCommandContext::parseCommandKey() {
806     if (key.empty()) {
807         command = "";
808     } else {
809         // The raw representing the key
810         const std::string statkey{reinterpret_cast<const char*>(key.data()),
811                                   key.size()};
812 
813         // Split the key into a command and argument.
814         auto index = statkey.find(' ');
815 
816         if (index == key.npos) {
817             command = statkey;
818         } else {
819             command = statkey.substr(0, index);
820             argument = statkey.substr(++index);
821         }
822     }
823 
824     state = State::CheckPrivilege;
825     return ENGINE_SUCCESS;
826 }
827 
checkPrivilege()828 ENGINE_ERROR_CODE StatsCommandContext::checkPrivilege() {
829     auto ret = ENGINE_SUCCESS;
830 
831     auto handler = getStatHandler(command).first;
832 
833     if (handler.privileged) {
834         ret = mcbp::checkPrivilege(cookie, cb::rbac::Privilege::Stats);
835         switch (ret) {
836         case ENGINE_SUCCESS:
837             state = State::DoStats;
838             break;
839         default:
840             command_exit_code = ret;
841             state = State::CommandComplete;
842             break;
843         }
844     } else {
845         state = State::DoStats;
846     }
847 
848     return ENGINE_SUCCESS;
849 }
850 
doStats()851 ENGINE_ERROR_CODE StatsCommandContext::doStats() {
852     auto handler_pair = getStatHandler(command);
853 
854     if (!handler_pair.second) {
855         command_exit_code = handler_pair.first.handler(
856                 {reinterpret_cast<const char*>(key.data()), key.size()},
857                 cookie);
858     } else {
859         command_exit_code = handler_pair.first.handler(argument, cookie);
860     }
861 
862     // If stats command call returns ENGINE_EWOULDBLOCK and the task is not a
863     // nullptr (ie we have created a background task), then change the state to
864     // be GetTaskResult and return ENGINE_EWOULDBLOCK
865     if (command_exit_code == ENGINE_EWOULDBLOCK && task) {
866         state = State::GetTaskResult;
867         return ENGINE_EWOULDBLOCK;
868     }
869 
870     state = State::CommandComplete;
871     return ENGINE_SUCCESS;
872 }
873 
getTaskResult()874 ENGINE_ERROR_CODE StatsCommandContext::getTaskResult() {
875     auto& stats_task = dynamic_cast<StatsTask&>(*task);
876 
877     state = State::CommandComplete;
878     command_exit_code = stats_task.getCommandError();
879     return ENGINE_SUCCESS;
880 }
881 
commandComplete()882 ENGINE_ERROR_CODE StatsCommandContext::commandComplete() {
883     switch (command_exit_code) {
884     case ENGINE_SUCCESS:
885         append_stats(nullptr, 0, nullptr, 0, static_cast<void*>(&cookie));
886 
887         // We just want to record this once rather than for each packet sent
888         ++connection.getBucket()
889                   .responseCounters[int(cb::mcbp::Status::Success)];
890         cookie.sendDynamicBuffer();
891         break;
892     case ENGINE_EWOULDBLOCK:
893         /* If the stats call returns ENGINE_EWOULDBLOCK then set the
894          * state to DoStats again and return this error code */
895         state = State::DoStats;
896         return command_exit_code;
897     case ENGINE_DISCONNECT:
898         // We don't send these responses back so we will not store
899         // stats for these.
900         break;
901     default:
902         ++connection.getBucket().responseCounters[int(
903                 cb::mcbp::to_status(cb::engine_errc(command_exit_code)))];
904         break;
905     }
906     state = State::Done;
907     return ENGINE_SUCCESS;
908 }
909