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 18#include "tracing.h" 19 20#include "cookie.h" 21#include "executorpool.h" 22#include "memcached.h" 23#include "task.h" 24#include "tracing_types.h" 25 26#include <daemon/protocol/mcbp/command_context.h> 27 28#include <chrono> 29#include <memory> 30#include <mutex> 31 32// TODO: MB-20640 The default config should be configurable from memcached.json 33static phosphor::TraceConfig lastConfig{ 34 phosphor::TraceConfig(phosphor::BufferMode::ring, 20 * 1024 * 1024)}; 35static std::mutex configMutex; 36 37ENGINE_ERROR_CODE ioctlGetTracingStatus(Cookie& cookie, 38 const StrToStrMap&, 39 std::string& value) { 40 value = PHOSPHOR_INSTANCE.isEnabled() ? "enabled" : "disabled"; 41 return ENGINE_SUCCESS; 42} 43 44ENGINE_ERROR_CODE ioctlGetTracingConfig(Cookie& cookie, 45 const StrToStrMap&, 46 std::string& value) { 47 std::lock_guard<std::mutex> lh(configMutex); 48 value = *lastConfig.toString(); 49 return ENGINE_SUCCESS; 50} 51 52template <typename ContainerT, typename PredicateT> 53void erase_if(ContainerT& items, const PredicateT& predicate) { 54 for (auto it = items.begin(); it != items.end();) { 55 if (predicate(*it)) { 56 it = items.erase(it); 57 } else { 58 ++it; 59 } 60 } 61} 62 63Task::Status StaleTraceDumpRemover::periodicExecute() { 64 const auto now = std::chrono::steady_clock::now(); 65 std::lock_guard<std::mutex> lh(traceDumps.mutex); 66 67 using value_type = decltype(TraceDumps::dumps)::value_type; 68 erase_if(traceDumps.dumps, [now, this](const value_type& dump) { 69 // If the mutex is locked then a 70 // chunk is being generated from this dump 71 auto isLocked = dump.second->mutex.try_lock(); 72 if (!isLocked) { 73 return false; 74 } 75 76 std::lock_guard<std::mutex> guard(dump.second->mutex, std::adopt_lock); 77 return (dump.second->last_touch + std::chrono::seconds(max_age)) <= now; 78 }); 79 return Status::Continue; // always repeat 80} 81 82static TraceDumps traceDumps; 83static std::shared_ptr<StaleTraceDumpRemover> dump_remover; 84 85void initializeTracing() { 86 // Currently just creating the stale dump remover periodic task 87 // @todo make period and max_age configurable 88 dump_remover = std::make_shared<StaleTraceDumpRemover>( 89 traceDumps, std::chrono::seconds(60), std::chrono::seconds(300)); 90 std::shared_ptr<Task> task = dump_remover; 91 { 92 std::lock_guard<std::mutex> lg(task->getMutex()); 93 executorPool->schedule(task); 94 } 95 96 // and begin tracing. 97 { 98 std::lock_guard<std::mutex> lh(configMutex); 99 PHOSPHOR_INSTANCE.start(lastConfig); 100 } 101} 102 103void deinitializeTracing() { 104 dump_remover.reset(); 105 phosphor::TraceLog::getInstance().stop(); 106 std::lock_guard<std::mutex> guard(traceDumps.mutex); 107 traceDumps.dumps.clear(); 108} 109 110ENGINE_ERROR_CODE ioctlGetTracingBeginDump(Cookie& cookie, 111 const StrToStrMap&, 112 std::string& value) { 113 std::lock_guard<phosphor::TraceLog> lh(PHOSPHOR_INSTANCE); 114 if (PHOSPHOR_INSTANCE.isEnabled()) { 115 PHOSPHOR_INSTANCE.stop(lh); 116 } 117 118 phosphor::TraceContext context = PHOSPHOR_INSTANCE.getTraceContext(lh); 119 if (context.getBuffer() == nullptr) { 120 cookie.setErrorContext( 121 "Cannot begin a dump when there is no existing trace"); 122 return ENGINE_EINVAL; 123 } 124 125 // Create the new dump associated with a random uuid 126 cb::uuid::uuid_t uuid = cb::uuid::random(); 127 { 128 std::lock_guard<std::mutex> lh(traceDumps.mutex); 129 traceDumps.dumps.emplace( 130 uuid, std::make_unique<DumpContext>(std::move(context))); 131 } 132 133 // Return the textual form of the uuid back to the user with success 134 value = to_string(uuid); 135 return ENGINE_SUCCESS; 136} 137 138/** 139 * A task for generating tasks in the background on an 140 * executor thread instead of a front-end thread 141 */ 142class ChunkBuilderTask : public Task { 143public: 144 /// This constructor assumes that the dump's 145 /// mutex has already been locked 146 ChunkBuilderTask(Cookie& cookie_, 147 DumpContext& dump, 148 std::unique_lock<std::mutex> lck, 149 size_t chunk_size) 150 : Task(), cookie(cookie_), dump(dump), lck(std::move(lck)) { 151 chunk.resize(chunk_size); 152 } 153 154 Status execute() override { 155 size_t count = dump.json_export.read(&chunk[0], chunk.size()); 156 chunk.resize(count); 157 return Status::Finished; 158 } 159 160 void notifyExecutionComplete() override { 161 notify_io_complete(&cookie, ENGINE_SUCCESS); 162 } 163 164 std::string& getChunk() { 165 return chunk; 166 } 167 168private: 169 std::string chunk; 170 Cookie& cookie; 171 DumpContext& dump; 172 std::unique_lock<std::mutex> lck; 173}; 174 175struct ChunkBuilderContext : public CommandContext { 176 ChunkBuilderContext(std::shared_ptr<ChunkBuilderTask>& task) : task(task) { 177 } 178 179 std::shared_ptr<ChunkBuilderTask> task; 180}; 181 182ENGINE_ERROR_CODE ioctlGetTracingDumpChunk(Cookie& cookie, 183 const StrToStrMap& arguments, 184 std::string& value) { 185 // If we have a context then we already generated the chunk 186 auto* ctx = dynamic_cast<ChunkBuilderContext*>(cookie.getCommandContext()); 187 if (ctx != nullptr) { 188 value = std::move(ctx->task->getChunk()); 189 cookie.setCommandContext(); 190 return ENGINE_SUCCESS; 191 } 192 193 auto id = arguments.find("id"); 194 if (id == arguments.end()) { 195 cookie.setErrorContext("Dump ID must be specified as a key argument"); 196 return ENGINE_EINVAL; 197 } 198 199 cb::uuid::uuid_t uuid; 200 try { 201 uuid = cb::uuid::from_string(id->second); 202 } catch (const std::invalid_argument&) { 203 cookie.setErrorContext("Dump ID must be a valid UUID"); 204 return ENGINE_EINVAL; 205 } 206 207 { 208 std::lock_guard<std::mutex> lh(traceDumps.mutex); 209 auto iter = traceDumps.dumps.find(uuid); 210 if (iter == traceDumps.dumps.end()) { 211 cookie.setErrorContext( 212 "Dump ID must correspond to an existing dump"); 213 return ENGINE_EINVAL; 214 } 215 auto& dump = *(iter->second); 216 217 // @todo make configurable 218 const size_t chunk_size = 1024 * 1024; 219 220 if (dump.json_export.done()) { 221 value = ""; 222 return ENGINE_SUCCESS; 223 } 224 225 std::unique_lock<std::mutex> lck(dump.mutex, std::try_to_lock); 226 // A chunk is already being generated for this dump 227 if (!lck) { 228 value = ""; 229 cookie.setErrorContext( 230 "A chunk is already being fetched for this dump"); 231 return ENGINE_TMPFAIL; 232 } 233 234 // ChunkBuilderTask assumes the lock above is already held 235 auto task = std::make_shared<ChunkBuilderTask>( 236 cookie, dump, std::move(lck), chunk_size); 237 cookie.setCommandContext(new ChunkBuilderContext{task}); 238 239 cookie.setEwouldblock(true); 240 std::lock_guard<std::mutex> guard(task->getMutex()); 241 std::shared_ptr<Task> basicTask = task; 242 executorPool->schedule(basicTask, true); 243 244 return ENGINE_EWOULDBLOCK; 245 } 246} 247 248ENGINE_ERROR_CODE ioctlSetTracingClearDump(Cookie& cookie, 249 const StrToStrMap& arguments, 250 const std::string& value) { 251 cb::uuid::uuid_t uuid; 252 try { 253 uuid = cb::uuid::from_string(value); 254 } catch (const std::invalid_argument&) { 255 cookie.setErrorContext("Dump ID must be a valid UUID"); 256 return ENGINE_EINVAL; 257 } 258 259 { 260 std::lock_guard<std::mutex> lh(traceDumps.mutex); 261 auto dump = traceDumps.dumps.find(uuid); 262 if (dump == traceDumps.dumps.end()) { 263 cookie.setErrorContext( 264 "Dump ID must correspond to an existing dump"); 265 return ENGINE_EINVAL; 266 } 267 268 traceDumps.dumps.erase(dump); 269 } 270 271 return ENGINE_SUCCESS; 272} 273 274ENGINE_ERROR_CODE ioctlSetTracingConfig(Cookie& cookie, 275 const StrToStrMap&, 276 const std::string& value) { 277 if (value.empty()) { 278 cookie.setErrorContext("Trace config cannot be empty"); 279 return ENGINE_EINVAL; 280 } 281 try { 282 std::lock_guard<std::mutex> lh(configMutex); 283 lastConfig = phosphor::TraceConfig::fromString(value); 284 } catch (const std::invalid_argument& e) { 285 cookie.setErrorContext(std::string("Trace config is illformed: ") + 286 e.what()); 287 return ENGINE_EINVAL; 288 } 289 return ENGINE_SUCCESS; 290} 291 292ENGINE_ERROR_CODE ioctlSetTracingStart(Cookie& cookie, 293 const StrToStrMap&, 294 const std::string& value) { 295 std::lock_guard<std::mutex> lh(configMutex); 296 PHOSPHOR_INSTANCE.start(lastConfig); 297 return ENGINE_SUCCESS; 298} 299 300ENGINE_ERROR_CODE ioctlSetTracingStop(Cookie& cookie, 301 const StrToStrMap&, 302 const std::string& value) { 303 PHOSPHOR_INSTANCE.stop(); 304 return ENGINE_SUCCESS; 305} 306