diff --git a/arangod/Aql/Query.cpp b/arangod/Aql/Query.cpp index a6e946dde8..cc20673383 100644 --- a/arangod/Aql/Query.cpp +++ b/arangod/Aql/Query.cpp @@ -34,6 +34,7 @@ #include "Aql/ExecutionPlan.h" #include "Aql/Optimizer.h" #include "Aql/Parser.h" +#include "Aql/QueryList.h" #include "Basics/JsonHelper.h" #include "Basics/json.h" #include "Basics/tri-strings.h" @@ -53,6 +54,10 @@ using Json = triagens::basics::Json; // --SECTION-- static const values // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @brief names of query phases / states +//////////////////////////////////////////////////////////////////////////////// + static std::string StateNames[] = { "initializing", // INITIALIZATION "parsing", // PARSING @@ -75,9 +80,26 @@ static_assert(sizeof(StateNames) / sizeof(std::string) == static_cast(Ex /// @brief create a profile //////////////////////////////////////////////////////////////////////////////// -Profile::Profile () - : results(static_cast(INVALID_STATE)), +Profile::Profile (Query* query) + : query(query), + results(static_cast(INVALID_STATE)), stamp(TRI_microtime()) { + + auto queryList = static_cast(query->vocbase()->_queries); + try { + queryList->insert(query, stamp); + } + catch (...) { + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief destroy a profile +//////////////////////////////////////////////////////////////////////////////// + +Profile::~Profile () { + auto queryList = static_cast(query->vocbase()->_queries); + queryList->remove(query, stamp); } //////////////////////////////////////////////////////////////////////////////// @@ -89,7 +111,7 @@ void Profile::enter (ExecutionState state) { if (state != ExecutionState::INVALID_STATE) { // record duration of state - results.push_back(std::make_pair(state, now - stamp)); + results.emplace_back(std::make_pair(state, now - stamp)); } // set timestamp @@ -128,7 +150,8 @@ Query::Query (triagens::arango::ApplicationV8* applicationV8, TRI_json_t* bindParameters, TRI_json_t* options, QueryPart part) - : _applicationV8(applicationV8), + : _id(TRI_NextQueryIdVocBase(vocbase)), + _applicationV8(applicationV8), _vocbase(vocbase), _executor(nullptr), _context(nullptr), @@ -155,9 +178,7 @@ Query::Query (triagens::arango::ApplicationV8* applicationV8, TRI_ASSERT(_vocbase != nullptr); - if (profiling()) { - _profile = new Profile; - } + _profile = new Profile(this); enterState(INITIALIZATION); _ast = new Ast(this); @@ -175,7 +196,8 @@ Query::Query (triagens::arango::ApplicationV8* applicationV8, triagens::basics::Json queryStruct, TRI_json_t* options, QueryPart part) - : _applicationV8(applicationV8), + : _id(TRI_NextQueryIdVocBase(vocbase)), + _applicationV8(applicationV8), _vocbase(vocbase), _executor(nullptr), _context(nullptr), @@ -202,9 +224,7 @@ Query::Query (triagens::arango::ApplicationV8* applicationV8, TRI_ASSERT(_vocbase != nullptr); - if (profiling()) { - _profile = new Profile; - } + _profile = new Profile(this); enterState(INITIALIZATION); _ast = new Ast(this); @@ -619,7 +639,7 @@ QueryResult Query::execute (QueryRegistry* registry) { result.json = jsonResult.steal(); result.stats = stats.steal(); - if (_profile != nullptr) { + if (_profile != nullptr && profiling()) { result.profile = _profile->toJson(TRI_UNKNOWN_MEM_ZONE); } @@ -691,7 +711,7 @@ QueryResultV8 Query::executeV8 (v8::Isolate* isolate, QueryRegistry* registry) { result.warnings = warningsToJson(TRI_UNKNOWN_MEM_ZONE); result.stats = stats.steal(); - if (_profile != nullptr) { + if (_profile != nullptr && profiling()) { result.profile = _profile->toJson(TRI_UNKNOWN_MEM_ZONE); } diff --git a/arangod/Aql/Query.h b/arangod/Aql/Query.h index 15612a03a9..5447d99d2c 100644 --- a/arangod/Aql/Query.h +++ b/arangod/Aql/Query.h @@ -38,6 +38,7 @@ #include "Aql/types.h" #include "Utils/AqlTransaction.h" #include "Utils/V8TransactionContext.h" +#include "VocBase/voc-types.h" #include "V8Server/ApplicationV8.h" struct TRI_json_t; @@ -57,6 +58,7 @@ namespace triagens { class Executor; class Expression; class Parser; + class Query; class QueryRegistry; struct Variable; @@ -106,12 +108,18 @@ namespace triagens { // ----------------------------------------------------------------------------- struct Profile { - Profile (); + Profile (Profile const&) = delete; + Profile& operator= (Profile const&) = delete; + + explicit Profile (Query*); + + ~Profile (); void enter (ExecutionState); TRI_json_t* toJson (TRI_memory_zone_t*); + Query* query; std::vector> results; double stamp; }; @@ -196,6 +204,14 @@ namespace triagens { return _collections.collectionNames(); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the query's id +//////////////////////////////////////////////////////////////////////////////// + + TRI_voc_tick_t id () const { + return _id; + } + //////////////////////////////////////////////////////////////////////////////// /// @brief get the query string //////////////////////////////////////////////////////////////////////////////// @@ -478,6 +494,12 @@ namespace triagens { private: +//////////////////////////////////////////////////////////////////////////////// +/// @brief query id +//////////////////////////////////////////////////////////////////////////////// + + TRI_voc_tick_t const _id; + //////////////////////////////////////////////////////////////////////////////// /// @brief application v8 used in the query, we need this for V8 context access //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/QueryList.cpp b/arangod/Aql/QueryList.cpp new file mode 100644 index 0000000000..b539528edb --- /dev/null +++ b/arangod/Aql/QueryList.cpp @@ -0,0 +1,279 @@ +//////////////////////////////////////////////////////////////////////////////// +/// @brief Aql, list of running queries +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2014 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Jan Steemann +/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany +/// @author Copyright 2012-2013, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +#include "Aql/QueryList.h" +#include "Aql/Query.h" +#include "Basics/ReadLocker.h" +#include "Basics/WriteLocker.h" +#include "VocBase/vocbase.h" + +using namespace triagens::aql; + +// ----------------------------------------------------------------------------- +// --SECTION-- struct QueryEntry +// ----------------------------------------------------------------------------- + +QueryEntry::QueryEntry (TRI_voc_tick_t id, + char const* queryString, + size_t queryLength, + double started) + : id(id), + queryString(queryString), + queryLength(queryLength), + started(started) { +} + +// ----------------------------------------------------------------------------- +// --SECTION-- struct QueryEntryCopy +// ----------------------------------------------------------------------------- + +QueryEntryCopy::QueryEntryCopy (TRI_voc_tick_t id, + std::string const& queryString, + double started, + double runTime) + : id(id), + queryString(queryString), + started(started), + runTime(runTime) { + +} + +// ----------------------------------------------------------------------------- +// --SECTION-- class QueryList +// ----------------------------------------------------------------------------- + +double const QueryList::DefaultSlowQueryThreshold = 10.0; +size_t const QueryList::DefaultMaxSlowQueries = 64; +size_t const QueryList::DefaultMaxQueryStringLength = 4096; + +// ----------------------------------------------------------------------------- +// --SECTION-- constructors / destructors +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a query list +//////////////////////////////////////////////////////////////////////////////// + +QueryList::QueryList (TRI_vocbase_t* vocbase) + : _vocbase(vocbase), + _lock(), + _current(), + _slow(), + _slowCount(0), + _enabled(false), + _trackSlowQueries(true), + _slowQueryThreshold(QueryList::DefaultSlowQueryThreshold), + _maxSlowQueries(QueryList::DefaultMaxSlowQueries), + _maxQueryStringLength(QueryList::DefaultMaxQueryStringLength) { + + _current.reserve(64); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief destroy a query list +//////////////////////////////////////////////////////////////////////////////// + +QueryList::~QueryList () { + WRITE_LOCKER(_lock); + + for (auto it : _current) { + delete it.second; + } + _current.clear(); + _slow.clear(); +} + +// ----------------------------------------------------------------------------- +// --SECTION-- public methods +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief insert a query +//////////////////////////////////////////////////////////////////////////////// + +void QueryList::insert (Query const* query, + double stamp) { + // no query string + if (query->queryString() == nullptr || ! _enabled) { + return; + } + + try { + std::unique_ptr entry(new QueryEntry( + query->id(), + query->queryString(), + query->queryLength(), + stamp + )); + + WRITE_LOCKER(_lock); + auto it = _current.emplace(std::make_pair(query->id(), entry.get())); + if (it.second) { + entry.release(); + } + } + catch (...) { + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief remove a query +//////////////////////////////////////////////////////////////////////////////// + +void QueryList::remove (Query const* query, + double now) { + // no query string + if (query->queryString() == nullptr || ! _enabled) { + return; + } + + QueryEntry* entry = nullptr; + + { + WRITE_LOCKER(_lock); + auto it = _current.find(query->id()); + + if (it != _current.end()) { + entry = (*it).second; + _current.erase(it); + + TRI_ASSERT(entry != nullptr); + + try { + // check if we need to push the query into the list of slow queries + if (_slowQueryThreshold >= 0.0 && + now - entry->started >= _slowQueryThreshold) { + // yes. + + size_t const maxLength = _maxQueryStringLength; + size_t length = entry->queryLength; + if (length > maxLength) { + length = maxLength; + } + + _slow.emplace_back(QueryEntryCopy( + entry->id, + std::string(entry->queryString, length).append(entry->queryLength > maxLength ? "..." : ""), + entry->started, + now - entry->started + )); + + if (++_slowCount > _maxSlowQueries) { + // free first element + _slow.pop_front(); + --_slowCount; + } + } + } + catch (...) { + } + } + } + + if (entry != nullptr) { + delete entry; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief get the list of currently running queries +//////////////////////////////////////////////////////////////////////////////// + +std::vector QueryList::listCurrent () { + double const now = TRI_microtime(); + + std::vector result; + + { + size_t const maxLength = _maxQueryStringLength; + + READ_LOCKER(_lock); + result.reserve(_current.size()); + + for (auto const& it : _current) { + auto entry = it.second; + + if (entry == nullptr || + entry->queryString == nullptr) { + continue; + } + + size_t length = entry->queryLength; + if (length > maxLength) { + length = maxLength; + } + + result.emplace_back(QueryEntryCopy( + entry->id, + std::string(entry->queryString, length).append(entry->queryLength > maxLength ? "..." : ""), + entry->started, + now - entry->started + )); + + + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief get the list of slow queries +//////////////////////////////////////////////////////////////////////////////// + +std::vector QueryList::listSlow () { + std::vector result; + + { + READ_LOCKER(_lock); + result.reserve(_slow.size()); + result.assign(_slow.begin(), _slow.end()); + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief clear the list of slow queries +//////////////////////////////////////////////////////////////////////////////// + +void QueryList::clearSlow () { + WRITE_LOCKER(_lock); + _slow.clear(); + _slowCount = 0; +} + +// ----------------------------------------------------------------------------- +// --SECTION-- END-OF-FILE +// ----------------------------------------------------------------------------- + +// Local Variables: +// mode: outline-minor +// outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}" +// End: diff --git a/arangod/Aql/QueryList.h b/arangod/Aql/QueryList.h new file mode 100644 index 0000000000..25c32148e3 --- /dev/null +++ b/arangod/Aql/QueryList.h @@ -0,0 +1,331 @@ +//////////////////////////////////////////////////////////////////////////////// +/// @brief Aql, list of queries running in database +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2014 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Jan Steemann +/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany +/// @author Copyright 2012-2013, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +#ifndef ARANGODB_AQL_QUERY_LIST_H +#define ARANGODB_AQL_QUERY_LIST_H 1 + +#include "Basics/Common.h" +#include "Basics/ReadWriteLock.h" +#include "VocBase/voc-types.h" + +struct TRI_vocbase_s; + +namespace triagens { + namespace aql { + + class Query; + +// ----------------------------------------------------------------------------- +// --SECTION-- struct QueryEntry +// ----------------------------------------------------------------------------- + + struct QueryEntry { + QueryEntry (TRI_voc_tick_t, + char const*, + size_t, + double); + + TRI_voc_tick_t const id; + char const* const queryString; + size_t const queryLength; + double const started; + }; + +// ----------------------------------------------------------------------------- +// --SECTION-- struct QueryEntryCopy +// ----------------------------------------------------------------------------- + + struct QueryEntryCopy { + QueryEntryCopy (TRI_voc_tick_t, + std::string const&, + double, + double); + + TRI_voc_tick_t id; + std::string queryString; + double started; + double runTime; + }; + +// ----------------------------------------------------------------------------- +// --SECTION-- class QueryList +// ----------------------------------------------------------------------------- + + class QueryList { + +// ----------------------------------------------------------------------------- +// --SECTION-- constructors / destructors +// ----------------------------------------------------------------------------- + + public: + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a query list +//////////////////////////////////////////////////////////////////////////////// + + explicit QueryList (struct TRI_vocbase_s*); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief destroy a query list +//////////////////////////////////////////////////////////////////////////////// + + ~QueryList (); + +// ----------------------------------------------------------------------------- +// --SECTION-- public methods +// ----------------------------------------------------------------------------- + + public: + +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not queries are tracked +//////////////////////////////////////////////////////////////////////////////// + + inline bool enabled () const { + return _enabled; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief toggle query tracking +//////////////////////////////////////////////////////////////////////////////// + + inline void enabled (bool value) { + _enabled = value; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not slow queries are tracked +//////////////////////////////////////////////////////////////////////////////// + + inline bool trackSlowQueries () const { + return _trackSlowQueries; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief toggle slow query tracking +//////////////////////////////////////////////////////////////////////////////// + + inline void trackSlowQueries (bool value) { + _trackSlowQueries = value; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief threshold for slow queries (in seconds) +//////////////////////////////////////////////////////////////////////////////// + + inline double slowQueryThreshold () const { + return _slowQueryThreshold; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set the slow query threshold +//////////////////////////////////////////////////////////////////////////////// + + inline void slowQueryThreshold (double value) { + if (value < 0.0 || value == HUGE_VAL || value != value) { + // sanity checks + value = 0.0; + } + _slowQueryThreshold = value; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the max number of slow queries to keep +//////////////////////////////////////////////////////////////////////////////// + + inline size_t maxSlowQueries () const { + return _maxSlowQueries; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set the max number of slow queries to keep +//////////////////////////////////////////////////////////////////////////////// + + inline void maxSlowQueries (size_t value) { + if (value > 16384) { + // sanity checks + value = 16384; + } + _maxSlowQueries = value; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the max length of query strings that are stored / returned +//////////////////////////////////////////////////////////////////////////////// + + inline size_t maxQueryStringLength () const { + return _maxQueryStringLength; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set the max length of query strings that are stored / returned +//////////////////////////////////////////////////////////////////////////////// + + inline void maxQueryStringLength (size_t value) { + // sanity checks + if (value < 64) { + value = 64; + } + else if (value >= 8 * 1024 * 1024) { + value = 8 * 1024 * 1024; + } + + _maxQueryStringLength = value; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief enter a query +//////////////////////////////////////////////////////////////////////////////// + + void insert (Query const*, + double); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief remove a query +//////////////////////////////////////////////////////////////////////////////// + + void remove (Query const*, + double); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the list of running queries +//////////////////////////////////////////////////////////////////////////////// + + std::vector listCurrent (); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the list of slow queries +//////////////////////////////////////////////////////////////////////////////// + + std::vector listSlow (); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief clear the list of slow queries +//////////////////////////////////////////////////////////////////////////////// + + void clearSlow (); + +// ----------------------------------------------------------------------------- +// --SECTION-- private variables +// ----------------------------------------------------------------------------- + + private: + +//////////////////////////////////////////////////////////////////////////////// +/// @brief vocbase +//////////////////////////////////////////////////////////////////////////////// + + struct TRI_vocbase_s* _vocbase; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief r/w lock for the list +//////////////////////////////////////////////////////////////////////////////// + + triagens::basics::ReadWriteLock _lock; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief list of current queries +//////////////////////////////////////////////////////////////////////////////// + + std::unordered_map _current; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief list of slow queries +//////////////////////////////////////////////////////////////////////////////// + + std::list _slow; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief current number of slow queries +//////////////////////////////////////////////////////////////////////////////// + + size_t _slowCount; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not queries are tracked +//////////////////////////////////////////////////////////////////////////////// + + bool _enabled; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not slow queries are tracked +//////////////////////////////////////////////////////////////////////////////// + + bool _trackSlowQueries; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief threshold for slow queries (in seconds) +//////////////////////////////////////////////////////////////////////////////// + + double _slowQueryThreshold; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief maximum number of slow queries to keep +//////////////////////////////////////////////////////////////////////////////// + + size_t _maxSlowQueries; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief max length of query strings to return +//////////////////////////////////////////////////////////////////////////////// + + size_t _maxQueryStringLength; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief default threshold for slow queries +//////////////////////////////////////////////////////////////////////////////// + + static double const DefaultSlowQueryThreshold; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief default maximum number of slow queries to keep in list +//////////////////////////////////////////////////////////////////////////////// + + static size_t const DefaultMaxSlowQueries; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief default max length of a query when returning it +//////////////////////////////////////////////////////////////////////////////// + + static size_t const DefaultMaxQueryStringLength; + }; + + } +} + +#endif + +// ----------------------------------------------------------------------------- +// --SECTION-- END-OF-FILE +// ----------------------------------------------------------------------------- + +// Local Variables: +// mode: outline-minor +// outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}" +// End: diff --git a/arangod/CMakeLists.txt b/arangod/CMakeLists.txt index b20a7ecddc..c09a94894a 100644 --- a/arangod/CMakeLists.txt +++ b/arangod/CMakeLists.txt @@ -56,6 +56,7 @@ add_executable( Aql/OptimizerRules.cpp Aql/Parser.cpp Aql/Query.cpp + Aql/QueryList.cpp Aql/QueryRegistry.cpp Aql/RangeInfo.cpp Aql/Range.cpp diff --git a/arangod/Makefile.files b/arangod/Makefile.files index ce3ae30e07..1750535e8a 100644 --- a/arangod/Makefile.files +++ b/arangod/Makefile.files @@ -37,6 +37,7 @@ arangod_libarangod_a_SOURCES = \ arangod/Aql/OptimizerRules.cpp \ arangod/Aql/Parser.cpp \ arangod/Aql/Query.cpp \ + arangod/Aql/QueryList.cpp \ arangod/Aql/QueryRegistry.cpp \ arangod/Aql/RangeInfo.cpp \ arangod/Aql/Range.cpp \ diff --git a/arangod/V8Server/v8-vocbase.cpp b/arangod/V8Server/v8-vocbase.cpp index 67a92aacfc..21eab869ed 100644 --- a/arangod/V8Server/v8-vocbase.cpp +++ b/arangod/V8Server/v8-vocbase.cpp @@ -37,6 +37,7 @@ #include "v8-voccursor.h" #include "Aql/Query.h" +#include "Aql/QueryList.h" #include "Aql/QueryRegistry.h" #include "Basics/Utf8Helper.h" @@ -107,7 +108,7 @@ int32_t const WRP_VOCBASE_COL_TYPE = 2; // ----------------------------------------------------------------------------- // --SECTION-- HELPER FUNCTIONS // ----------------------------------------------------------------------------- - + //////////////////////////////////////////////////////////////////////////////// /// @brief wraps a C++ into a v8::Object //////////////////////////////////////////////////////////////////////////////// @@ -1406,6 +1407,166 @@ static void JS_ExecuteAql (const v8::FunctionCallbackInfo& args) { TRI_WrapGeneralCursor(args, cursor); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief retrieve global query options or configure them +//////////////////////////////////////////////////////////////////////////////// + +static void JS_QueriesPropertiesAql (const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + + TRI_vocbase_t* vocbase = GetContextVocBase(isolate); + + if (vocbase == nullptr) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); + } + + auto queryList = static_cast(vocbase->_queries); + TRI_ASSERT(queryList != nullptr); + + + if (args.Length() > 1) { + TRI_V8_THROW_EXCEPTION_USAGE("AQL_QUERIES_PROPERTIES()"); + } + + if (args.Length() == 1) { + // store options + if (! args[0]->IsObject()) { + TRI_V8_THROW_EXCEPTION_USAGE("AQL_QUERIES_PROPERTIES()"); + } + + auto obj = args[0]->ToObject(); + if (obj->Has(TRI_V8_ASCII_STRING("enabled"))) { + queryList->enabled(TRI_ObjectToBoolean(obj->Get(TRI_V8_ASCII_STRING("enabled")))); + } + if (obj->Has(TRI_V8_ASCII_STRING("trackSlowQueries"))) { + queryList->trackSlowQueries(TRI_ObjectToBoolean(obj->Get(TRI_V8_ASCII_STRING("trackSlowQueries")))); + } + if (obj->Has(TRI_V8_ASCII_STRING("maxSlowQueries"))) { + queryList->maxSlowQueries(static_cast(TRI_ObjectToInt64(obj->Get(TRI_V8_ASCII_STRING("maxSlowQueries"))))); + } + if (obj->Has(TRI_V8_ASCII_STRING("slowQueryThreshold"))) { + queryList->slowQueryThreshold(TRI_ObjectToDouble(obj->Get(TRI_V8_ASCII_STRING("slowQueryThreshold")))); + } + if (obj->Has(TRI_V8_ASCII_STRING("maxQueryStringLength"))) { + queryList->maxQueryStringLength(static_cast(TRI_ObjectToInt64(obj->Get(TRI_V8_ASCII_STRING("maxQueryStringLength"))))); + } + + // fall-through intentional + } + + // return current settings + auto result = v8::Object::New(isolate); + result->Set(TRI_V8_ASCII_STRING("enabled"), v8::Boolean::New(isolate, queryList->enabled())); + result->Set(TRI_V8_ASCII_STRING("trackSlowQueries"), v8::Boolean::New(isolate, queryList->trackSlowQueries())); + result->Set(TRI_V8_ASCII_STRING("maxSlowQueries"), v8::Number::New(isolate, static_cast(queryList->maxSlowQueries()))); + result->Set(TRI_V8_ASCII_STRING("slowQueryThreshold"), v8::Number::New(isolate, queryList->slowQueryThreshold())); + result->Set(TRI_V8_ASCII_STRING("maxQueryStringLength"), v8::Number::New(isolate, static_cast(queryList->maxQueryStringLength()))); + + TRI_V8_RETURN(result); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns the list of currently running queries +//////////////////////////////////////////////////////////////////////////////// + +static void JS_QueriesCurrentAql (const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + + TRI_vocbase_t* vocbase = GetContextVocBase(isolate); + + if (vocbase == nullptr) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); + } + + + if (args.Length() != 0) { + TRI_V8_THROW_EXCEPTION_USAGE("AQL_QUERIES_CURRENT()"); + } + + + auto queryList = static_cast(vocbase->_queries); + TRI_ASSERT(queryList != nullptr); + + + try { + auto const&& queries = queryList->listCurrent(); + + uint32_t i = 0; + auto result = v8::Array::New(isolate, static_cast(queries.size())); + + for (auto it : queries) { + auto const&& timeString = TRI_StringTimeStamp(it.started); + + v8::Handle obj = v8::Object::New(isolate); + obj->Set(TRI_V8_ASCII_STRING("id"), V8TickId(isolate, it.id)); + obj->Set(TRI_V8_ASCII_STRING("query"), TRI_V8_STD_STRING(it.queryString)); + obj->Set(TRI_V8_ASCII_STRING("started"), TRI_V8_STD_STRING(timeString)); + obj->Set(TRI_V8_ASCII_STRING("runTime"), v8::Number::New(isolate, it.runTime)); + + result->Set(i++, obj); + } + + TRI_V8_RETURN(result); + } + catch (...) { + TRI_V8_THROW_EXCEPTION_MEMORY(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns the list of slow running queries or clears the list +//////////////////////////////////////////////////////////////////////////////// + +static void JS_QueriesSlowAql (const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + + TRI_vocbase_t* vocbase = GetContextVocBase(isolate); + + if (vocbase == nullptr) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); + } + + auto queryList = static_cast(vocbase->_queries); + TRI_ASSERT(queryList != nullptr); + + + if (args.Length() == 1) { + queryList->clearSlow(); + TRI_V8_RETURN_TRUE(); + } + + if (args.Length() != 0) { + TRI_V8_THROW_EXCEPTION_USAGE("AQL_QUERIES_SLOW()"); + } + + try { + auto const&& queries = queryList->listSlow(); + + uint32_t i = 0; + auto result = v8::Array::New(isolate, static_cast(queries.size())); + + for (auto it : queries) { + auto const&& timeString = TRI_StringTimeStamp(it.started); + + v8::Handle obj = v8::Object::New(isolate); + obj->Set(TRI_V8_ASCII_STRING("id"), V8TickId(isolate, it.id)); + obj->Set(TRI_V8_ASCII_STRING("query"), TRI_V8_STD_STRING(it.queryString)); + obj->Set(TRI_V8_ASCII_STRING("started"), TRI_V8_STD_STRING(timeString)); + obj->Set(TRI_V8_ASCII_STRING("runTime"), v8::Number::New(isolate, it.runTime)); + + result->Set(i++, obj); + } + + TRI_V8_RETURN(result); + } + catch (...) { + TRI_V8_THROW_EXCEPTION_MEMORY(); + } +} + // ----------------------------------------------------------------------------- // --SECTION-- TRI_VOCBASE_T FUNCTIONS // ----------------------------------------------------------------------------- @@ -2678,6 +2839,9 @@ void TRI_InitV8VocBridge (v8::Isolate* isolate, TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_EXPLAIN"), JS_ExplainAql, true); TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_PARSE"), JS_ParseAql, true); TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_WARNING"), JS_WarningAql, true); + TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_QUERIES_PROPERTIES"), JS_QueriesPropertiesAql, true); + TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_QUERIES_CURRENT"), JS_QueriesCurrentAql, true); + TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_QUERIES_SLOW"), JS_QueriesSlowAql, true); TRI_InitV8replication(isolate, context, server, vocbase, loader, threadNumber, v8g); diff --git a/arangod/VocBase/vocbase.cpp b/arangod/VocBase/vocbase.cpp index a7cd1aa673..08bc2822aa 100644 --- a/arangod/VocBase/vocbase.cpp +++ b/arangod/VocBase/vocbase.cpp @@ -35,6 +35,7 @@ #include +#include "Aql/QueryList.h" #include "Basics/conversions.h" #include "Basics/files.h" #include "Basics/hashes.h" @@ -74,6 +75,8 @@ // --SECTION-- private types // ----------------------------------------------------------------------------- +static std::atomic QueryId(1); + //////////////////////////////////////////////////////////////////////////////// /// @brief auxiliary struct for index iteration //////////////////////////////////////////////////////////////////////////////// @@ -1350,6 +1353,18 @@ TRI_vocbase_t* TRI_CreateInitialVocBase (TRI_server_t* server, vocbase->_oldTransactions = nullptr; + try { + vocbase->_queries = new triagens::aql::QueryList(vocbase); + } + catch (...) { + TRI_Free(TRI_CORE_MEM_ZONE, vocbase->_name); + TRI_Free(TRI_CORE_MEM_ZONE, vocbase->_path); + TRI_Free(TRI_UNKNOWN_MEM_ZONE, vocbase); + TRI_set_errno(TRI_ERROR_OUT_OF_MEMORY); + + return nullptr; + } + // use the defaults provided TRI_ApplyVocBaseDefaults(vocbase, defaults); @@ -1436,6 +1451,10 @@ void TRI_DestroyInitialVocBase (TRI_vocbase_t* vocbase) { TRI_DestroySpin(&vocbase->_usage._lock); TRI_FreeStoreGeneralCursor(vocbase->_cursors); + + if (vocbase->_queries != nullptr) { + delete static_cast(vocbase->_queries); + } // free name and path TRI_Free(TRI_CORE_MEM_ZONE, vocbase->_path); @@ -2479,6 +2498,14 @@ bool TRI_IsAllowedNameVocBase (bool allowSystem, return true; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns the next query id +//////////////////////////////////////////////////////////////////////////////// + +TRI_voc_tick_t TRI_NextQueryIdVocBase (TRI_vocbase_t* vocbase) { + return QueryId.fetch_add(1, std::memory_order_seq_cst); +} + // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE // ----------------------------------------------------------------------------- diff --git a/arangod/VocBase/vocbase.h b/arangod/VocBase/vocbase.h index b5a9544c31..d690c284af 100644 --- a/arangod/VocBase/vocbase.h +++ b/arangod/VocBase/vocbase.h @@ -311,6 +311,7 @@ typedef struct TRI_vocbase_s { // structures for user-defined volatile data void* _userStructures; + void* _queries; TRI_associative_pointer_t _authInfo; TRI_associative_pointer_t _authCache; @@ -648,6 +649,12 @@ bool TRI_IsSystemVocBase (TRI_vocbase_t*); bool TRI_IsAllowedNameVocBase (bool, char const*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns the next query id +//////////////////////////////////////////////////////////////////////////////// + +TRI_voc_tick_t TRI_NextQueryIdVocBase (TRI_vocbase_t*); + #endif // ----------------------------------------------------------------------------- diff --git a/js/actions/api-query.js b/js/actions/api-query.js index 06dff82efc..d4426ab124 100644 --- a/js/actions/api-query.js +++ b/js/actions/api-query.js @@ -1,5 +1,5 @@ /*jshint strict: false */ -/*global require, AQL_PARSE */ +/*global require, AQL_PARSE, AQL_QUERIES_CURRENT, AQL_QUERIES_SLOW, AQL_QUERIES_PROPERTIES */ //////////////////////////////////////////////////////////////////////////////// /// @brief query actions @@ -34,9 +34,165 @@ var actions = require("org/arangodb/actions"); var ArangoError = arangodb.ArangoError; // ----------------------------------------------------------------------------- -// --SECTION-- global variables +// --SECTION-- HTTP methods // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @startDocuBlock GetApiQueryCurrent +/// @brief returns a list of currently running AQL queries +/// +/// @RESTHEADER{GET /_api/query/current, Returns the currently running AQL queries} +/// +/// @RESTRETURNCODES +/// +/// @RESTRETURNCODE{200} +/// Is returned when the list of queries can be retrieved successfully. +/// +/// @RESTRETURNCODE{400} +/// The server will respond with *HTTP 400* in case of a malformed request, +/// +/// @endDocuBlock +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +/// @startDocuBlock GetApiQuerySlow +/// @brief returns a list of slow running AQL queries +/// +/// @RESTHEADER{GET /_api/query/slow, Returns the list of slow AQL queries} +/// +/// @RESTRETURNCODES +/// +/// @RESTRETURNCODE{200} +/// Is returned when the list of queries can be retrieved successfully. +/// +/// @RESTRETURNCODE{400} +/// The server will respond with *HTTP 400* in case of a malformed request, +/// +/// @endDocuBlock +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +/// @startDocuBlock GetApiQueryProperties +/// @brief returns the configuration for the AQL query tracking +/// +/// @RESTHEADER{GET /_api/query/properties, Returns the properties for the AQL query tracking} +/// +/// @RESTRETURNCODES +/// +/// @RESTRETURNCODE{200} +/// Is returned when the list of queries can be retrieved successfully. +/// +/// @RESTRETURNCODE{400} +/// The server will respond with *HTTP 400* in case of a malformed request, +/// +/// @endDocuBlock +//////////////////////////////////////////////////////////////////////////////// + +function get_api_query (req, res) { + var suffixes = [ "slow", "current", "properties" ]; + + if (req.suffix.length !== 1 || + suffixes.indexOf(req.suffix[0]) === -1) { + actions.resultNotFound(req, + res, + arangodb.ERROR_HTTP_NOT_FOUND, + arangodb.errors.ERROR_HTTP_NOT_FOUND.message); + return; + } + + var result; + if (req.suffix[0] === "slow") { + result = AQL_QUERIES_SLOW(); + } + else if (req.suffix[0] === "current") { + result = AQL_QUERIES_CURRENT(); + } + else if (req.suffix[0] === "properties") { + result = AQL_QUERIES_PROPERTIES(); + } + + if (result instanceof ArangoError) { + actions.resultBad(req, res, result.errorNum, result.errorMessage); + return; + } + + actions.resultOk(req, res, actions.HTTP_OK, result); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @startDocuBlock DeleteApiQuerySlow +/// @brief clears the list of slow AQL queries +/// +/// @RESTHEADER{DELETE /_api/query/slow, Clears the list of slow AQL queries} +/// +/// @RESTRETURNCODES +/// +/// @RESTRETURNCODE{204} +/// The server will respond with *HTTP 200* when the list of queries was +/// cleared successfully. +/// +/// @RESTRETURNCODE{400} +/// The server will respond with *HTTP 400* in case of a malformed request. +/// @endDocuBlock +//////////////////////////////////////////////////////////////////////////////// + +function delete_api_query (req, res) { + if (req.suffix.length !== 1 || req.suffix[0] !== "slow") { + actions.resultNotFound(req, + res, + arangodb.ERROR_HTTP_NOT_FOUND, + arangodb.errors.ERROR_HTTP_NOT_FOUND.message); + return; + } + + AQL_QUERIES_SLOW(true); + actions.resultOk(req, res, actions.HTTP_NO_CONTENT); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @startDocuBlock PutApiQueryProperties +/// @brief chages the configuration for the AQL query tracking +/// +/// @RESTHEADER{PUT /_api/query/properties, Changes the properties for the AQL query tracking} +/// +/// @RESTRETURNCODES +/// +/// @RESTRETURNCODE{200} +/// Is returned if the properties were changed successfully. +/// +/// @RESTRETURNCODE{400} +/// The server will respond with *HTTP 400* in case of a malformed request, +/// +/// @endDocuBlock +//////////////////////////////////////////////////////////////////////////////// + +function put_api_query (req, res) { + + if (req.suffix.length !== 1 || + req.suffix[0] !== "properties") { + actions.resultNotFound(req, + res, + arangodb.ERROR_HTTP_NOT_FOUND, + arangodb.errors.ERROR_HTTP_NOT_FOUND.message); + return; + } + + var json = actions.getJsonBody(req, res); + + if (json === undefined) { + return; + } + + var result = AQL_QUERIES_PROPERTIES(json); + + if (result instanceof ArangoError) { + actions.resultBad(req, res, result.errorNum, result.errorMessage); + return; + } + + actions.resultOk(req, res, actions.HTTP_OK, result); +} + //////////////////////////////////////////////////////////////////////////////// /// @startDocuBlock JSF_post_api_query /// @brief parse an AQL query and return information about it @@ -144,10 +300,22 @@ actions.defineHttp({ callback : function (req, res) { try { switch (req.requestType) { + case actions.GET: + get_api_query(req, res); + break; + + case actions.PUT: + put_api_query(req, res); + break; + case actions.POST: post_api_query(req, res); break; - + + case actions.DELETE: + delete_api_query(req, res); + break; + default: actions.resultUnsupported(req, res); } diff --git a/lib/Basics/conversions.cpp b/lib/Basics/conversions.cpp index d82ccfbbc1..c05ec944e5 100644 --- a/lib/Basics/conversions.cpp +++ b/lib/Basics/conversions.cpp @@ -904,6 +904,22 @@ char* TRI_StringUInt64Octal (uint64_t attr) { return TRI_DuplicateString2(buffer, len); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief converts a time stamp to a string +//////////////////////////////////////////////////////////////////////////////// + +std::string TRI_StringTimeStamp (double stamp) { + char buffer[32]; + size_t len; + struct tm tb; + time_t tt = static_cast(stamp); + + TRI_gmtime(tt, &tb); + len = strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", &tb); + + return std::string(buffer, len); +} + // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE // ----------------------------------------------------------------------------- diff --git a/lib/Basics/conversions.h b/lib/Basics/conversions.h index 431b71d353..6b3d949a9c 100644 --- a/lib/Basics/conversions.h +++ b/lib/Basics/conversions.h @@ -318,6 +318,12 @@ char* TRI_StringUInt32Octal (uint32_t); char* TRI_StringUInt64Octal (uint64_t); +//////////////////////////////////////////////////////////////////////////////// +/// @brief converts a time stamp to a string +//////////////////////////////////////////////////////////////////////////////// + +std::string TRI_StringTimeStamp (double); + #endif // -----------------------------------------------------------------------------