/******************************************************************************
* Icinga 2 *
- * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
+ * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
#include "livestatus/orfilter.hpp"
#include "livestatus/andfilter.hpp"
#include "icinga/externalcommandprocessor.hpp"
-#include "config/configcompiler.hpp"
#include "base/debug.hpp"
#include "base/convert.hpp"
#include "base/objectlock.hpp"
#include "base/timer.hpp"
#include "base/initialize.hpp"
#include <boost/algorithm/string/classification.hpp>
-#include <boost/foreach.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/join.hpp>
static int l_ExternalCommands = 0;
static boost::mutex l_QueryMutex;
-static std::map<String, LivestatusScriptFrame> l_LivestatusScriptFrames;
-static Timer::Ptr l_FrameCleanupTimer;
-static boost::mutex l_LivestatusScriptMutex;
-
-static void ScriptFrameCleanupHandler(void)
-{
- boost::mutex::scoped_lock lock(l_LivestatusScriptMutex);
-
- std::vector<String> cleanup_keys;
-
- typedef std::pair<String, LivestatusScriptFrame> KVPair;
-
- BOOST_FOREACH(const KVPair& kv, l_LivestatusScriptFrames) {
- if (kv.second.Seen < Utility::GetTime() - 60)
- cleanup_keys.push_back(kv.first);
- }
-
- BOOST_FOREACH(const String& key, cleanup_keys)
- l_LivestatusScriptFrames.erase(key);
-}
-
-static void InitScriptFrameCleanup(void)
-{
- l_FrameCleanupTimer = new Timer();
- l_FrameCleanupTimer->OnTimerExpired.connect(boost::bind(ScriptFrameCleanupHandler));
- l_FrameCleanupTimer->SetInterval(30);
- l_FrameCleanupTimer->Start();
-}
-
-INITIALIZE_ONCE(InitScriptFrameCleanup);
LivestatusQuery::LivestatusQuery(const std::vector<String>& lines, const String& compat_log_path)
- : m_KeepAlive(false), m_OutputFormat("csv"), m_ColumnHeaders(true),
- m_LogTimeFrom(0), m_LogTimeUntil(static_cast<long>(Utility::GetTime()))
+ : m_KeepAlive(false), m_OutputFormat("csv"), m_ColumnHeaders(true), m_Limit(-1), m_ErrorCode(0),
+ m_LogTimeFrom(0), m_LogTimeUntil(static_cast<long>(Utility::GetTime()))
{
if (lines.size() == 0) {
m_Verb = "ERROR";
}
String msg;
- BOOST_FOREACH(const String& line, lines) {
+ for (const String& line : lines) {
msg += line + "\n";
}
Log(LogDebug, "LivestatusQuery", msg);
m_CompatLogPath = compat_log_path;
/* default separators */
- m_Separators.push_back("\n");
- m_Separators.push_back(";");
- m_Separators.push_back(",");
- m_Separators.push_back("|");
+ m_Separators.emplace_back("\n");
+ m_Separators.emplace_back(";");
+ m_Separators.emplace_back(",");
+ m_Separators.emplace_back("|");
String line = lines[0];
if (m_Verb == "COMMAND") {
m_KeepAlive = true;
m_Command = target;
- } else if (m_Verb == "SCRIPT") {
- m_Session = target;
-
- for (unsigned int i = 1; i < lines.size(); i++) {
- if (m_Command != "")
- m_Command += "\n";
- m_Command += lines[i];
- }
-
- return;
} else if (m_Verb == "GET") {
m_Table = target;
} else {
//OutputFormat:json or OutputFormat: json
if (line.GetLength() > col_index + 1)
- params = line.SubStr(col_index + 1);
-
- params.Trim();
+ params = line.SubStr(col_index + 1).Trim();
if (header == "ResponseHeader")
m_ResponseHeader = params;
m_Separators[3] = String(1, static_cast<char>(Convert::ToLong(separators[3])));
} else if (header == "ColumnHeaders")
m_ColumnHeaders = (params == "on");
+ else if (header == "Limit")
+ m_Limit = Convert::ToLong(params);
else if (header == "Filter") {
Filter::Ptr filter = ParseFilter(params, m_LogTimeFrom, m_LogTimeUntil);
if (header == "Or" || header == "StatsOr") {
filter = new OrFilter();
Log(LogDebug, "LivestatusQuery")
- << "Add OR filter for " << params << " column(s). " << deq.size() << " filters available.";
+ << "Add OR filter for " << params << " column(s). " << deq.size() << " filters available.";
} else {
filter = new AndFilter();
Log(LogDebug, "LivestatusQuery")
- << "Add AND filter for " << params << " column(s). " << deq.size() << " filters available.";
+ << "Add AND filter for " << params << " column(s). " << deq.size() << " filters available.";
}
if (num > deq.size()) {
while (num > 0 && num--) {
filter->AddSubFilter(deq.back());
Log(LogDebug, "LivestatusQuery")
- << "Add " << num << " filter.";
+ << "Add " << num << " filter.";
deq.pop_back();
if (&deq == &stats)
aggregators.pop_back();
}
- deq.push_back(filter);
+ deq.emplace_back(filter);
if (&deq == &stats) {
Aggregator::Ptr aggregator = new CountAggregator();
aggregator->SetFilter(filter);
/* Combine all top-level filters into a single filter. */
AndFilter::Ptr top_filter = new AndFilter();
- BOOST_FOREACH(const Filter::Ptr& filter, filters) {
+ for (const Filter::Ptr& filter : filters) {
top_filter->AddSubFilter(filter);
}
m_Aggregators.swap(aggregators);
}
-int LivestatusQuery::GetExternalCommands(void)
+int LivestatusQuery::GetExternalCommands()
{
boost::mutex::scoped_lock lock(l_QueryMutex);
break;
}
- tokens.push_back(temp_buffer.SubStr(0, sp_index));
+ tokens.emplace_back(temp_buffer.SubStr(0, sp_index));
temp_buffer = temp_buffer.SubStr(sp_index + 1);
}
/* add the rest as value */
- tokens.push_back(temp_buffer);
+ tokens.emplace_back(std::move(temp_buffer));
if (tokens.size() == 2)
- tokens.push_back("");
+ tokens.emplace_back("");
if (tokens.size() < 3)
- return Filter::Ptr();
+ return nullptr;
bool negate = false;
String attr = tokens[0];
}
Log(LogDebug, "LivestatusQuery")
- << "Parsed filter with attr: '" << attr << "' op: '" << op << "' val: '" << val << "'.";
+ << "Parsed filter with attr: '" << attr << "' op: '" << op << "' val: '" << val << "'.";
return filter;
}
-void LivestatusQuery::PrintResultSet(std::ostream& fp, const Array::Ptr& rs) const
+void LivestatusQuery::BeginResultSet(std::ostream& fp) const
{
- if (m_OutputFormat == "csv") {
- ObjectLock olock(rs);
-
- BOOST_FOREACH(const Array::Ptr& row, rs) {
- bool first = true;
-
- ObjectLock rlock(row);
- BOOST_FOREACH(const Value& value, row) {
- if (first)
- first = false;
- else
- fp << m_Separators[1];
-
- if (value.IsObjectType<Array>())
- PrintCsvArray(fp, value, 0);
- else
- fp << value;
- }
+ if (m_OutputFormat == "json" || m_OutputFormat == "python")
+ fp << "[";
+}
+
+void LivestatusQuery::EndResultSet(std::ostream& fp) const
+{
+ if (m_OutputFormat == "json" || m_OutputFormat == "python")
+ fp << "]";
+}
- fp << m_Separators[0];
+void LivestatusQuery::AppendResultRow(std::ostream& fp, const Array::Ptr& row, bool& first_row) const
+{
+ if (m_OutputFormat == "csv") {
+ bool first = true;
+
+ ObjectLock rlock(row);
+ for (const Value& value : row) {
+ if (first)
+ first = false;
+ else
+ fp << m_Separators[1];
+
+ if (value.IsObjectType<Array>())
+ PrintCsvArray(fp, value, 0);
+ else
+ fp << value;
}
+
+ fp << m_Separators[0];
} else if (m_OutputFormat == "json") {
- fp << JsonEncode(rs);
+ if (!first_row)
+ fp << ", ";
+
+ fp << JsonEncode(row);
} else if (m_OutputFormat == "python") {
- PrintPythonArray(fp, rs);
+ if (!first_row)
+ fp << ", ";
+
+ PrintPythonArray(fp, row);
}
+
+ first_row = false;
}
void LivestatusQuery::PrintCsvArray(std::ostream& fp, const Array::Ptr& array, int level) const
bool first = true;
ObjectLock olock(array);
- BOOST_FOREACH(const Value& value, array) {
+ for (const Value& value : array) {
if (first)
first = false;
else
if (value.IsObjectType<Array>())
PrintCsvArray(fp, value, level + 1);
+ else if (value.IsBoolean())
+ fp << Convert::ToLong(value);
else
fp << value;
}
bool first = true;
- BOOST_FOREACH(const Value& value, rs) {
+ for (const Value& value : rs) {
if (first)
first = false;
else
void LivestatusQuery::ExecuteGetHelper(const Stream::Ptr& stream)
{
- Log(LogInformation, "LivestatusQuery")
- << "Table: " << m_Table;
+ Log(LogNotice, "LivestatusQuery")
+ << "Table: " << m_Table;
Table::Ptr table = Table::GetByName(m_Table, m_CompatLogPath, m_LogTimeFrom, m_LogTimeUntil);
return;
}
- std::vector<Value> objects = table->FilterRows(m_Filter);
+ std::vector<LivestatusRowValue> objects = table->FilterRows(m_Filter, m_Limit);
std::vector<String> columns;
if (m_Columns.size() > 0)
else
columns = table->GetColumnNames();
- Array::Ptr rs = new Array();
+ std::ostringstream result;
+ bool first_row = true;
+ BeginResultSet(result);
if (m_Aggregators.empty()) {
Array::Ptr header = new Array();
- BOOST_FOREACH(const Value& object, objects) {
+ typedef std::pair<String, Column> ColumnPair;
+
+ std::vector<ColumnPair> column_objs;
+ column_objs.reserve(columns.size());
+
+ for (const String& columnName : columns)
+ column_objs.emplace_back(columnName, table->GetColumn(columnName));
+
+ for (const LivestatusRowValue& object : objects) {
Array::Ptr row = new Array();
- BOOST_FOREACH(const String& columnName, columns) {
- Column column = table->GetColumn(columnName);
+ row->Reserve(column_objs.size());
+ for (const ColumnPair& cv : column_objs) {
if (m_ColumnHeaders)
- header->Add(columnName);
+ header->Add(cv.first);
- row->Add(column.ExtractValue(object));
+ row->Add(cv.second.ExtractValue(object.Row, object.GroupByType, object.GroupByObject));
}
if (m_ColumnHeaders) {
- rs->Add(header);
+ AppendResultRow(result, header, first_row);
m_ColumnHeaders = false;
}
- rs->Add(row);
+ AppendResultRow(result, row, first_row);
}
} else {
- std::vector<double> stats(m_Aggregators.size(), 0);
- int index = 0;
+ std::map<std::vector<Value>, std::vector<AggregatorState *> > allStats;
/* add aggregated stats */
- BOOST_FOREACH(const Aggregator::Ptr aggregator, m_Aggregators) {
- BOOST_FOREACH(const Value& object, objects) {
- aggregator->Apply(table, object);
+ for (const LivestatusRowValue& object : objects) {
+ std::vector<Value> statsKey;
+
+ for (const String& columnName : m_Columns) {
+ Column column = table->GetColumn(columnName);
+ statsKey.emplace_back(column.ExtractValue(object.Row, object.GroupByType, object.GroupByObject));
+ }
+
+ auto it = allStats.find(statsKey);
+
+ if (it == allStats.end()) {
+ std::vector<AggregatorState *> newStats(m_Aggregators.size(), nullptr);
+ it = allStats.insert(std::make_pair(statsKey, newStats)).first;
}
- stats[index] = aggregator->GetResult();
- index++;
+ auto& stats = it->second;
+
+ int index = 0;
+
+ for (const Aggregator::Ptr& aggregator : m_Aggregators) {
+ aggregator->Apply(table, object.Row, &stats[index]);
+ index++;
+ }
}
/* add column headers both for raw and aggregated data */
if (m_ColumnHeaders) {
Array::Ptr header = new Array();
- BOOST_FOREACH(const String& columnName, m_Columns) {
+ for (const String& columnName : m_Columns) {
header->Add(columnName);
}
header->Add("stats_" + Convert::ToString(i));
}
- rs->Add(header);
+ AppendResultRow(result, header, first_row);
}
- Array::Ptr row = new Array();
+ for (const auto& kv : allStats) {
+ Array::Ptr row = new Array();
- /*
- * add selected columns next to stats
- * may not be accurate for grouping!
- */
- if (objects.size() > 0 && m_Columns.size() > 0) {
- BOOST_FOREACH(const String& columnName, m_Columns) {
- Column column = table->GetColumn(columnName);
+ row->Reserve(m_Columns.size() + m_Aggregators.size());
- row->Add(column.ExtractValue(objects[0])); // first object wins
+ for (const Value& keyPart : kv.first) {
+ row->Add(keyPart);
}
+
+ auto& stats = kv.second;
+
+ for (size_t i = 0; i < m_Aggregators.size(); i++)
+ row->Add(m_Aggregators[i]->GetResultAndFreeState(stats[i]));
+
+ AppendResultRow(result, row, first_row);
}
- for (size_t i = 0; i < m_Aggregators.size(); i++)
- row->Add(stats[i]);
+ /* add a bogus zero value if aggregated is empty*/
+ if (allStats.empty()) {
+ Array::Ptr row = new Array();
- rs->Add(row);
+ for (size_t i = 1; i <= m_Aggregators.size(); i++) {
+ row->Add(0);
+ }
+
+ AppendResultRow(result, row, first_row);
+ }
}
- std::ostringstream result;
- PrintResultSet(result, rs);
+ EndResultSet(result);
SendResponse(stream, LivestatusErrorOK, result.str());
}
l_ExternalCommands++;
}
- Log(LogInformation, "LivestatusQuery")
- << "Executing command: " << m_Command;
+ Log(LogNotice, "LivestatusQuery")
+ << "Executing command: " << m_Command;
ExternalCommandProcessor::Execute(m_Command);
SendResponse(stream, LivestatusErrorOK, "");
}
-void LivestatusQuery::ExecuteScriptHelper(const Stream::Ptr& stream)
-{
- Log(LogInformation, "LivestatusQuery")
- << "Executing expression: " << m_Command;
-
- m_ResponseHeader = "fixed16";
-
- LivestatusScriptFrame& lsf = l_LivestatusScriptFrames[m_Session];
- lsf.Seen = Utility::GetTime();
-
- if (!lsf.Locals)
- lsf.Locals = new Dictionary();
-
- String fileName = "<" + Convert::ToString(lsf.NextLine) + ">";
- lsf.NextLine++;
-
- lsf.Lines[fileName] = m_Command;
-
- Expression *expr = ConfigCompiler::CompileText(fileName, m_Command);
- Value result;
- try {
- ScriptFrame frame;
- frame.Locals = lsf.Locals;
- result = expr->Evaluate(frame);
- } catch (const ScriptError& ex) {
- delete expr;
-
- DebugInfo di = ex.GetDebugInfo();
-
- std::ostringstream msgbuf;
-
- msgbuf << di.Path << ": " << lsf.Lines[di.Path] << "\n"
- << String(di.Path.GetLength() + 2, ' ')
- << String(di.FirstColumn, ' ') << String(di.LastColumn - di.FirstColumn + 1, '^') << "\n"
- << ex.what() << "\n";
-
- SendResponse(stream, LivestatusErrorQuery, msgbuf.str());
- return;
- } catch (...) {
- delete expr;
- throw;
- }
- delete expr;
- SendResponse(stream, LivestatusErrorOK, JsonEncode(Serialize(result, FAState | FAConfig), true));
-}
-
void LivestatusQuery::ExecuteErrorHelper(const Stream::Ptr& stream)
{
Log(LogDebug, "LivestatusQuery")
- << "ERROR: Code: '" << m_ErrorCode << "' Message: '" << m_ErrorMessage << "'.";
+ << "ERROR: Code: '" << m_ErrorCode << "' Message: '" << m_ErrorMessage << "'.";
SendResponse(stream, m_ErrorCode, m_ErrorMessage);
}
try {
stream->Write(data.CStr(), data.GetLength());
} catch (const std::exception&) {
- Log(LogCritical, "LivestatusQuery", "Cannot write to TCP socket.");
+ Log(LogCritical, "LivestatusQuery", "Cannot write query response to socket.");
}
}
}
bool LivestatusQuery::Execute(const Stream::Ptr& stream)
{
try {
- Log(LogInformation, "LivestatusQuery")
- << "Executing livestatus query: " << m_Verb;
+ Log(LogNotice, "LivestatusQuery")
+ << "Executing livestatus query: " << m_Verb;
if (m_Verb == "GET")
ExecuteGetHelper(stream);
else if (m_Verb == "COMMAND")
ExecuteCommandHelper(stream);
- else if (m_Verb == "SCRIPT")
- ExecuteScriptHelper(stream);
else if (m_Verb == "ERROR")
ExecuteErrorHelper(stream);
else