summaryrefslogtreecommitdiffstats
path: root/lib/livestatus
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:32:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:32:39 +0000
commit56ae875861ab260b80a030f50c4aff9f9dc8fff0 (patch)
tree531412110fc901a5918c7f7442202804a83cada9 /lib/livestatus
parentInitial commit. (diff)
downloadicinga2-upstream/2.14.2.tar.xz
icinga2-upstream/2.14.2.zip
Adding upstream version 2.14.2.upstream/2.14.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--lib/livestatus/CMakeLists.txt65
-rw-r--r--lib/livestatus/aggregator.cpp18
-rw-r--r--lib/livestatus/aggregator.hpp44
-rw-r--r--lib/livestatus/andfilter.cpp15
-rw-r--r--lib/livestatus/andfilter.hpp26
-rw-r--r--lib/livestatus/attributefilter.cpp121
-rw-r--r--lib/livestatus/attributefilter.hpp33
-rw-r--r--lib/livestatus/avgaggregator.cpp38
-rw-r--r--lib/livestatus/avgaggregator.hpp42
-rw-r--r--lib/livestatus/column.cpp21
-rw-r--r--lib/livestatus/column.hpp37
-rw-r--r--lib/livestatus/combinerfilter.cpp10
-rw-r--r--lib/livestatus/combinerfilter.hpp31
-rw-r--r--lib/livestatus/commandstable.cpp142
-rw-r--r--lib/livestatus/commandstable.hpp41
-rw-r--r--lib/livestatus/commentstable.cpp178
-rw-r--r--lib/livestatus/commentstable.hpp49
-rw-r--r--lib/livestatus/contactgroupstable.cpp74
-rw-r--r--lib/livestatus/contactgroupstable.hpp39
-rw-r--r--lib/livestatus/contactstable.cpp278
-rw-r--r--lib/livestatus/contactstable.hpp50
-rw-r--r--lib/livestatus/countaggregator.cpp30
-rw-r--r--lib/livestatus/countaggregator.hpp37
-rw-r--r--lib/livestatus/downtimestable.cpp168
-rw-r--r--lib/livestatus/downtimestable.hpp51
-rw-r--r--lib/livestatus/endpointstable.cpp109
-rw-r--r--lib/livestatus/endpointstable.hpp41
-rw-r--r--lib/livestatus/filter.hpp28
-rw-r--r--lib/livestatus/historytable.hpp24
-rw-r--r--lib/livestatus/hostgroupstable.cpp473
-rw-r--r--lib/livestatus/hostgroupstable.hpp61
-rw-r--r--lib/livestatus/hoststable.cpp1517
-rw-r--r--lib/livestatus/hoststable.hpp133
-rw-r--r--lib/livestatus/i2-livestatus.hpp14
-rw-r--r--lib/livestatus/invavgaggregator.cpp38
-rw-r--r--lib/livestatus/invavgaggregator.hpp42
-rw-r--r--lib/livestatus/invsumaggregator.cpp37
-rw-r--r--lib/livestatus/invsumaggregator.hpp41
-rw-r--r--lib/livestatus/livestatuslistener.cpp211
-rw-r--r--lib/livestatus/livestatuslistener.hpp47
-rw-r--r--lib/livestatus/livestatuslistener.ti31
-rw-r--r--lib/livestatus/livestatuslogutility.cpp321
-rw-r--r--lib/livestatus/livestatuslogutility.hpp60
-rw-r--r--lib/livestatus/livestatusquery.cpp648
-rw-r--r--lib/livestatus/livestatusquery.hpp90
-rw-r--r--lib/livestatus/logtable.cpp229
-rw-r--r--lib/livestatus/logtable.hpp65
-rw-r--r--lib/livestatus/maxaggregator.cpp38
-rw-r--r--lib/livestatus/maxaggregator.hpp41
-rw-r--r--lib/livestatus/minaggregator.cpp45
-rw-r--r--lib/livestatus/minaggregator.hpp42
-rw-r--r--lib/livestatus/negatefilter.cpp14
-rw-r--r--lib/livestatus/negatefilter.hpp31
-rw-r--r--lib/livestatus/orfilter.cpp18
-rw-r--r--lib/livestatus/orfilter.hpp26
-rw-r--r--lib/livestatus/servicegroupstable.cpp323
-rw-r--r--lib/livestatus/servicegroupstable.hpp54
-rw-r--r--lib/livestatus/servicestable.cpp1200
-rw-r--r--lib/livestatus/servicestable.hpp115
-rw-r--r--lib/livestatus/statehisttable.cpp466
-rw-r--r--lib/livestatus/statehisttable.hpp75
-rw-r--r--lib/livestatus/statustable.cpp269
-rw-r--r--lib/livestatus/statustable.hpp61
-rw-r--r--lib/livestatus/stdaggregator.cpp40
-rw-r--r--lib/livestatus/stdaggregator.hpp43
-rw-r--r--lib/livestatus/sumaggregator.cpp37
-rw-r--r--lib/livestatus/sumaggregator.hpp41
-rw-r--r--lib/livestatus/table.cpp165
-rw-r--r--lib/livestatus/table.hpp73
-rw-r--r--lib/livestatus/timeperiodstable.cpp58
-rw-r--r--lib/livestatus/timeperiodstable.hpp39
-rw-r--r--lib/livestatus/zonestable.cpp92
-rw-r--r--lib/livestatus/zonestable.hpp40
73 files changed, 9344 insertions, 0 deletions
diff --git a/lib/livestatus/CMakeLists.txt b/lib/livestatus/CMakeLists.txt
new file mode 100644
index 0000000..d49f9f5
--- /dev/null
+++ b/lib/livestatus/CMakeLists.txt
@@ -0,0 +1,65 @@
+# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+
+
+mkclass_target(livestatuslistener.ti livestatuslistener-ti.cpp livestatuslistener-ti.hpp)
+
+set(livestatus_SOURCES
+ i2-livestatus.hpp
+ aggregator.cpp aggregator.hpp
+ andfilter.cpp andfilter.hpp
+ attributefilter.cpp attributefilter.hpp
+ avgaggregator.cpp avgaggregator.hpp
+ column.cpp column.hpp
+ combinerfilter.cpp combinerfilter.hpp
+ commandstable.cpp commandstable.hpp
+ commentstable.cpp commentstable.hpp
+ contactgroupstable.cpp contactgroupstable.hpp
+ contactstable.cpp contactstable.hpp
+ countaggregator.cpp countaggregator.hpp
+ downtimestable.cpp downtimestable.hpp
+ endpointstable.cpp endpointstable.hpp
+ filter.hpp
+ historytable.hpp
+ hostgroupstable.cpp hostgroupstable.hpp
+ hoststable.cpp hoststable.hpp
+ invavgaggregator.cpp invavgaggregator.hpp
+ invsumaggregator.cpp invsumaggregator.hpp
+ livestatuslistener.cpp livestatuslistener.hpp livestatuslistener-ti.hpp
+ livestatuslogutility.cpp livestatuslogutility.hpp
+ livestatusquery.cpp livestatusquery.hpp
+ logtable.cpp logtable.hpp
+ maxaggregator.cpp maxaggregator.hpp
+ minaggregator.cpp minaggregator.hpp
+ negatefilter.cpp negatefilter.hpp
+ orfilter.cpp orfilter.hpp
+ servicegroupstable.cpp servicegroupstable.hpp
+ servicestable.cpp servicestable.hpp
+ statehisttable.cpp statehisttable.hpp
+ statustable.cpp statustable.hpp
+ stdaggregator.cpp stdaggregator.hpp
+ sumaggregator.cpp sumaggregator.hpp
+ table.cpp table.hpp
+ timeperiodstable.cpp timeperiodstable.hpp
+ zonestable.cpp zonestable.hpp
+)
+
+if(ICINGA2_UNITY_BUILD)
+ mkunity_target(livestatus livestatus livestatus_SOURCES)
+endif()
+
+add_library(livestatus OBJECT ${livestatus_SOURCES})
+
+add_dependencies(livestatus base config icinga remote)
+
+set_target_properties (
+ livestatus PROPERTIES
+ FOLDER Components
+)
+
+install_if_not_exists(
+ ${PROJECT_SOURCE_DIR}/etc/icinga2/features-available/livestatus.conf
+ ${ICINGA2_CONFIGDIR}/features-available
+)
+
+install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_INITRUNDIR}/cmd\")")
+
+set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}" PARENT_SCOPE)
diff --git a/lib/livestatus/aggregator.cpp b/lib/livestatus/aggregator.cpp
new file mode 100644
index 0000000..a809b07
--- /dev/null
+++ b/lib/livestatus/aggregator.cpp
@@ -0,0 +1,18 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/aggregator.hpp"
+
+using namespace icinga;
+
+void Aggregator::SetFilter(const Filter::Ptr& filter)
+{
+ m_Filter = filter;
+}
+
+Filter::Ptr Aggregator::GetFilter() const
+{
+ return m_Filter;
+}
+
+AggregatorState::~AggregatorState()
+{ }
diff --git a/lib/livestatus/aggregator.hpp b/lib/livestatus/aggregator.hpp
new file mode 100644
index 0000000..1c0f778
--- /dev/null
+++ b/lib/livestatus/aggregator.hpp
@@ -0,0 +1,44 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef AGGREGATOR_H
+#define AGGREGATOR_H
+
+#include "livestatus/i2-livestatus.hpp"
+#include "livestatus/table.hpp"
+#include "livestatus/filter.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+struct AggregatorState
+{
+ virtual ~AggregatorState();
+};
+
+/**
+ * @ingroup livestatus
+ */
+class Aggregator : public Object
+{
+public:
+ DECLARE_PTR_TYPEDEFS(Aggregator);
+
+ virtual void Apply(const Table::Ptr& table, const Value& row, AggregatorState **state) = 0;
+ virtual double GetResultAndFreeState(AggregatorState *state) const = 0;
+ void SetFilter(const Filter::Ptr& filter);
+
+protected:
+ Aggregator() = default;
+
+ Filter::Ptr GetFilter() const;
+
+private:
+ Filter::Ptr m_Filter;
+};
+
+}
+
+#endif /* AGGREGATOR_H */
diff --git a/lib/livestatus/andfilter.cpp b/lib/livestatus/andfilter.cpp
new file mode 100644
index 0000000..9852580
--- /dev/null
+++ b/lib/livestatus/andfilter.cpp
@@ -0,0 +1,15 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/andfilter.hpp"
+
+using namespace icinga;
+
+bool AndFilter::Apply(const Table::Ptr& table, const Value& row)
+{
+ for (const Filter::Ptr& filter : m_Filters) {
+ if (!filter->Apply(table, row))
+ return false;
+ }
+
+ return true;
+}
diff --git a/lib/livestatus/andfilter.hpp b/lib/livestatus/andfilter.hpp
new file mode 100644
index 0000000..8192bf7
--- /dev/null
+++ b/lib/livestatus/andfilter.hpp
@@ -0,0 +1,26 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef ANDFILTER_H
+#define ANDFILTER_H
+
+#include "livestatus/combinerfilter.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class AndFilter final : public CombinerFilter
+{
+public:
+ DECLARE_PTR_TYPEDEFS(AndFilter);
+
+ bool Apply(const Table::Ptr& table, const Value& row) override;
+};
+
+}
+
+#endif /* ANDFILTER_H */
diff --git a/lib/livestatus/attributefilter.cpp b/lib/livestatus/attributefilter.cpp
new file mode 100644
index 0000000..50d7244
--- /dev/null
+++ b/lib/livestatus/attributefilter.cpp
@@ -0,0 +1,121 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/attributefilter.hpp"
+#include "base/convert.hpp"
+#include "base/array.hpp"
+#include "base/objectlock.hpp"
+#include "base/logger.hpp"
+#include <boost/regex.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+using namespace icinga;
+
+AttributeFilter::AttributeFilter(String column, String op, String operand)
+ : m_Column(std::move(column)), m_Operator(std::move(op)), m_Operand(std::move(operand))
+{ }
+
+bool AttributeFilter::Apply(const Table::Ptr& table, const Value& row)
+{
+ Column column = table->GetColumn(m_Column);
+
+ Value value = column.ExtractValue(row);
+
+ if (value.IsObjectType<Array>()) {
+ Array::Ptr array = value;
+
+ if (m_Operator == ">=" || m_Operator == "<") {
+ bool negate = (m_Operator == "<");
+
+ ObjectLock olock(array);
+ for (const String& item : array) {
+ if (item == m_Operand)
+ return !negate; /* Item found in list. */
+ }
+
+ return negate; /* Item not found in list. */
+ } else if (m_Operator == "=") {
+ return (array->GetLength() == 0);
+ } else {
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid operator for column '" + m_Column + "': " + m_Operator + " (expected '>=' or '=')."));
+ }
+ } else {
+ if (m_Operator == "=") {
+ if (value.GetType() == ValueNumber || value.GetType() == ValueBoolean)
+ return (static_cast<double>(value) == Convert::ToDouble(m_Operand));
+ else
+ return (static_cast<String>(value) == m_Operand);
+ } else if (m_Operator == "~") {
+ bool ret;
+ try {
+ boost::regex expr(m_Operand.GetData());
+ String operand = value;
+ boost::smatch what;
+ ret = boost::regex_search(operand.GetData(), what, expr);
+ } catch (boost::exception&) {
+ Log(LogWarning, "AttributeFilter")
+ << "Regex '" << m_Operand << " " << m_Operator << " " << value << "' error.";
+ ret = false;
+ }
+
+ //Log(LogDebug, "LivestatusListener/AttributeFilter")
+ // << "Attribute filter '" << m_Operand + " " << m_Operator << " "
+ // << value << "' " << (ret ? "matches" : "doesn't match") << ".";
+
+ return ret;
+ } else if (m_Operator == "=~") {
+ bool ret;
+ try {
+ String operand = value;
+ ret = boost::iequals(operand, m_Operand.GetData());
+ } catch (boost::exception&) {
+ Log(LogWarning, "AttributeFilter")
+ << "Case-insensitive equality '" << m_Operand << " " << m_Operator << " " << value << "' error.";
+ ret = false;
+ }
+
+ return ret;
+ } else if (m_Operator == "~~") {
+ bool ret;
+ try {
+ boost::regex expr(m_Operand.GetData(), boost::regex::icase);
+ String operand = value;
+ boost::smatch what;
+ ret = boost::regex_search(operand.GetData(), what, expr);
+ } catch (boost::exception&) {
+ Log(LogWarning, "AttributeFilter")
+ << "Regex '" << m_Operand << " " << m_Operator << " " << value << "' error.";
+ ret = false;
+ }
+
+ //Log(LogDebug, "LivestatusListener/AttributeFilter")
+ // << "Attribute filter '" << m_Operand << " " << m_Operator << " "
+ // << value << "' " << (ret ? "matches" : "doesn't match") << ".";
+
+ return ret;
+ } else if (m_Operator == "<") {
+ if (value.GetType() == ValueNumber)
+ return (static_cast<double>(value) < Convert::ToDouble(m_Operand));
+ else
+ return (static_cast<String>(value) < m_Operand);
+ } else if (m_Operator == ">") {
+ if (value.GetType() == ValueNumber)
+ return (static_cast<double>(value) > Convert::ToDouble(m_Operand));
+ else
+ return (static_cast<String>(value) > m_Operand);
+ } else if (m_Operator == "<=") {
+ if (value.GetType() == ValueNumber)
+ return (static_cast<double>(value) <= Convert::ToDouble(m_Operand));
+ else
+ return (static_cast<String>(value) <= m_Operand);
+ } else if (m_Operator == ">=") {
+ if (value.GetType() == ValueNumber)
+ return (static_cast<double>(value) >= Convert::ToDouble(m_Operand));
+ else
+ return (static_cast<String>(value) >= m_Operand);
+ } else {
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Unknown operator for column '" + m_Column + "': " + m_Operator));
+ }
+ }
+
+ return false;
+}
diff --git a/lib/livestatus/attributefilter.hpp b/lib/livestatus/attributefilter.hpp
new file mode 100644
index 0000000..18bd843
--- /dev/null
+++ b/lib/livestatus/attributefilter.hpp
@@ -0,0 +1,33 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef ATTRIBUTEFILTER_H
+#define ATTRIBUTEFILTER_H
+
+#include "livestatus/filter.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class AttributeFilter final : public Filter
+{
+public:
+ DECLARE_PTR_TYPEDEFS(AttributeFilter);
+
+ AttributeFilter(String column, String op, String operand);
+
+ bool Apply(const Table::Ptr& table, const Value& row) override;
+
+protected:
+ String m_Column;
+ String m_Operator;
+ String m_Operand;
+};
+
+}
+
+#endif /* FILTER_H */
diff --git a/lib/livestatus/avgaggregator.cpp b/lib/livestatus/avgaggregator.cpp
new file mode 100644
index 0000000..35701f3
--- /dev/null
+++ b/lib/livestatus/avgaggregator.cpp
@@ -0,0 +1,38 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/avgaggregator.hpp"
+
+using namespace icinga;
+
+AvgAggregator::AvgAggregator(String attr)
+ : m_AvgAttr(std::move(attr))
+{ }
+
+AvgAggregatorState *AvgAggregator::EnsureState(AggregatorState **state)
+{
+ if (!*state)
+ *state = new AvgAggregatorState();
+
+ return static_cast<AvgAggregatorState *>(*state);
+}
+
+void AvgAggregator::Apply(const Table::Ptr& table, const Value& row, AggregatorState **state)
+{
+ Column column = table->GetColumn(m_AvgAttr);
+
+ Value value = column.ExtractValue(row);
+
+ AvgAggregatorState *pstate = EnsureState(state);
+
+ pstate->Avg += value;
+ pstate->AvgCount++;
+}
+
+double AvgAggregator::GetResultAndFreeState(AggregatorState *state) const
+{
+ AvgAggregatorState *pstate = EnsureState(&state);
+ double result = pstate->Avg / pstate->AvgCount;
+ delete pstate;
+
+ return result;
+}
diff --git a/lib/livestatus/avgaggregator.hpp b/lib/livestatus/avgaggregator.hpp
new file mode 100644
index 0000000..11bd9f3
--- /dev/null
+++ b/lib/livestatus/avgaggregator.hpp
@@ -0,0 +1,42 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef AVGAGGREGATOR_H
+#define AVGAGGREGATOR_H
+
+#include "livestatus/table.hpp"
+#include "livestatus/aggregator.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+struct AvgAggregatorState final : public AggregatorState
+{
+ double Avg{0};
+ double AvgCount{0};
+};
+
+/**
+ * @ingroup livestatus
+ */
+class AvgAggregator final : public Aggregator
+{
+public:
+ DECLARE_PTR_TYPEDEFS(AvgAggregator);
+
+ AvgAggregator(String attr);
+
+ void Apply(const Table::Ptr& table, const Value& row, AggregatorState **state) override;
+ double GetResultAndFreeState(AggregatorState *state) const override;
+
+private:
+ String m_AvgAttr;
+
+ static AvgAggregatorState *EnsureState(AggregatorState **state);
+};
+
+}
+
+#endif /* AVGAGGREGATOR_H */
diff --git a/lib/livestatus/column.cpp b/lib/livestatus/column.cpp
new file mode 100644
index 0000000..c915b3d
--- /dev/null
+++ b/lib/livestatus/column.cpp
@@ -0,0 +1,21 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/column.hpp"
+
+using namespace icinga;
+
+Column::Column(ValueAccessor valueAccessor, ObjectAccessor objectAccessor)
+ : m_ValueAccessor(std::move(valueAccessor)), m_ObjectAccessor(std::move(objectAccessor))
+{ }
+
+Value Column::ExtractValue(const Value& urow, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject) const
+{
+ Value row;
+
+ if (m_ObjectAccessor)
+ row = m_ObjectAccessor(urow, groupByType, groupByObject);
+ else
+ row = urow;
+
+ return m_ValueAccessor(row);
+}
diff --git a/lib/livestatus/column.hpp b/lib/livestatus/column.hpp
new file mode 100644
index 0000000..264cca7
--- /dev/null
+++ b/lib/livestatus/column.hpp
@@ -0,0 +1,37 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef COLUMN_H
+#define COLUMN_H
+
+#include "livestatus/i2-livestatus.hpp"
+#include "base/value.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+enum LivestatusGroupByType {
+ LivestatusGroupByNone,
+ LivestatusGroupByHostGroup,
+ LivestatusGroupByServiceGroup
+};
+
+class Column
+{
+public:
+ typedef std::function<Value (const Value&)> ValueAccessor;
+ typedef std::function<Value (const Value&, LivestatusGroupByType, const Object::Ptr&)> ObjectAccessor;
+
+ Column(ValueAccessor valueAccessor, ObjectAccessor objectAccessor);
+
+ Value ExtractValue(const Value& urow, LivestatusGroupByType groupByType = LivestatusGroupByNone, const Object::Ptr& groupByObject = Empty) const;
+
+private:
+ ValueAccessor m_ValueAccessor;
+ ObjectAccessor m_ObjectAccessor;
+};
+
+}
+
+#endif /* COLUMN_H */
diff --git a/lib/livestatus/combinerfilter.cpp b/lib/livestatus/combinerfilter.cpp
new file mode 100644
index 0000000..36a8328
--- /dev/null
+++ b/lib/livestatus/combinerfilter.cpp
@@ -0,0 +1,10 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/combinerfilter.hpp"
+
+using namespace icinga;
+
+void CombinerFilter::AddSubFilter(const Filter::Ptr& filter)
+{
+ m_Filters.push_back(filter);
+}
diff --git a/lib/livestatus/combinerfilter.hpp b/lib/livestatus/combinerfilter.hpp
new file mode 100644
index 0000000..49b8b61
--- /dev/null
+++ b/lib/livestatus/combinerfilter.hpp
@@ -0,0 +1,31 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef COMBINERFILTER_H
+#define COMBINERFILTER_H
+
+#include "livestatus/filter.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class CombinerFilter : public Filter
+{
+public:
+ DECLARE_PTR_TYPEDEFS(CombinerFilter);
+
+ void AddSubFilter(const Filter::Ptr& filter);
+
+protected:
+ std::vector<Filter::Ptr> m_Filters;
+
+ CombinerFilter() = default;
+};
+
+}
+
+#endif /* COMBINERFILTER_H */
diff --git a/lib/livestatus/commandstable.cpp b/lib/livestatus/commandstable.cpp
new file mode 100644
index 0000000..3a777d2
--- /dev/null
+++ b/lib/livestatus/commandstable.cpp
@@ -0,0 +1,142 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/commandstable.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "icinga/compatutility.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/convert.hpp"
+#include <boost/algorithm/string/replace.hpp>
+
+using namespace icinga;
+
+CommandsTable::CommandsTable()
+{
+ AddColumns(this);
+}
+
+void CommandsTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "name", Column(&CommandsTable::NameAccessor, objectAccessor));
+ table->AddColumn(prefix + "line", Column(&CommandsTable::LineAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variable_names", Column(&CommandsTable::CustomVariableNamesAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variable_values", Column(&CommandsTable::CustomVariableValuesAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variables", Column(&CommandsTable::CustomVariablesAccessor, objectAccessor));
+ table->AddColumn(prefix + "modified_attributes", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "modified_attributes_list", Column(&Table::ZeroAccessor, objectAccessor));
+}
+
+String CommandsTable::GetName() const
+{
+ return "commands";
+}
+
+String CommandsTable::GetPrefix() const
+{
+ return "command";
+}
+
+void CommandsTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ for (const ConfigObject::Ptr& object : ConfigType::GetObjectsByType<CheckCommand>()) {
+ if (!addRowFn(object, LivestatusGroupByNone, Empty))
+ return;
+ }
+
+ for (const ConfigObject::Ptr& object : ConfigType::GetObjectsByType<EventCommand>()) {
+ if (!addRowFn(object, LivestatusGroupByNone, Empty))
+ return;
+ }
+
+ for (const ConfigObject::Ptr& object : ConfigType::GetObjectsByType<NotificationCommand>()) {
+ if (!addRowFn(object, LivestatusGroupByNone, Empty))
+ return;
+ }
+}
+
+Value CommandsTable::NameAccessor(const Value& row)
+{
+ Command::Ptr command = static_cast<Command::Ptr>(row);
+
+ return CompatUtility::GetCommandName(command);
+}
+
+Value CommandsTable::LineAccessor(const Value& row)
+{
+ Command::Ptr command = static_cast<Command::Ptr>(row);
+
+ if (!command)
+ return Empty;
+
+ return CompatUtility::GetCommandLine(command);
+}
+
+Value CommandsTable::CustomVariableNamesAccessor(const Value& row)
+{
+ Command::Ptr command = static_cast<Command::Ptr>(row);
+
+ if (!command)
+ return Empty;
+
+ Dictionary::Ptr vars = command->GetVars();
+
+ ArrayData keys;
+
+ if (vars) {
+ ObjectLock xlock(vars);
+ for (const auto& kv : vars) {
+ keys.push_back(kv.first);
+ }
+ }
+
+ return new Array(std::move(keys));
+}
+
+Value CommandsTable::CustomVariableValuesAccessor(const Value& row)
+{
+ Command::Ptr command = static_cast<Command::Ptr>(row);
+
+ if (!command)
+ return Empty;
+
+ Dictionary::Ptr vars = command->GetVars();
+
+ ArrayData keys;
+
+ if (vars) {
+ ObjectLock xlock(vars);
+ for (const auto& kv : vars) {
+ keys.push_back(kv.second);
+ }
+ }
+
+ return new Array(std::move(keys));
+}
+
+Value CommandsTable::CustomVariablesAccessor(const Value& row)
+{
+ Command::Ptr command = static_cast<Command::Ptr>(row);
+
+ if (!command)
+ return Empty;
+
+ Dictionary::Ptr vars = command->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock xlock(vars);
+ for (const auto& kv : vars) {
+ result.push_back(new Array({
+ kv.first,
+ kv.second
+ }));
+ }
+ }
+
+ return new Array(std::move(result));
+}
diff --git a/lib/livestatus/commandstable.hpp b/lib/livestatus/commandstable.hpp
new file mode 100644
index 0000000..cd2d915
--- /dev/null
+++ b/lib/livestatus/commandstable.hpp
@@ -0,0 +1,41 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef COMMANDSTABLE_H
+#define COMMANDSTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class CommandsTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(CommandsTable);
+
+ CommandsTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Value NameAccessor(const Value& row);
+ static Value LineAccessor(const Value& row);
+ static Value CustomVariableNamesAccessor(const Value& row);
+ static Value CustomVariableValuesAccessor(const Value& row);
+ static Value CustomVariablesAccessor(const Value& row);
+};
+
+}
+
+#endif /* COMMANDSTABLE_H */
diff --git a/lib/livestatus/commentstable.cpp b/lib/livestatus/commentstable.cpp
new file mode 100644
index 0000000..40bffad
--- /dev/null
+++ b/lib/livestatus/commentstable.cpp
@@ -0,0 +1,178 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/commentstable.hpp"
+#include "livestatus/hoststable.hpp"
+#include "livestatus/servicestable.hpp"
+#include "icinga/service.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+
+using namespace icinga;
+
+CommentsTable::CommentsTable()
+{
+ AddColumns(this);
+}
+
+void CommentsTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "author", Column(&CommentsTable::AuthorAccessor, objectAccessor));
+ table->AddColumn(prefix + "comment", Column(&CommentsTable::CommentAccessor, objectAccessor));
+ table->AddColumn(prefix + "id", Column(&CommentsTable::IdAccessor, objectAccessor));
+ table->AddColumn(prefix + "entry_time", Column(&CommentsTable::EntryTimeAccessor, objectAccessor));
+ table->AddColumn(prefix + "type", Column(&CommentsTable::TypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "is_service", Column(&CommentsTable::IsServiceAccessor, objectAccessor));
+ table->AddColumn(prefix + "persistent", Column(&Table::OneAccessor, objectAccessor));
+ table->AddColumn(prefix + "source", Column(&Table::OneAccessor, objectAccessor));
+ table->AddColumn(prefix + "entry_type", Column(&CommentsTable::EntryTypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "expires", Column(&CommentsTable::ExpiresAccessor, objectAccessor));
+ table->AddColumn(prefix + "expire_time", Column(&CommentsTable::ExpireTimeAccessor, objectAccessor));
+
+ /* order is important - host w/o services must not be empty */
+ ServicesTable::AddColumns(table, "service_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return ServiceAccessor(row, objectAccessor);
+ });
+ HostsTable::AddColumns(table, "host_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return HostAccessor(row, objectAccessor);
+ });
+}
+
+String CommentsTable::GetName() const
+{
+ return "comments";
+}
+
+String CommentsTable::GetPrefix() const
+{
+ return "comment";
+}
+
+void CommentsTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ for (const Comment::Ptr& comment : ConfigType::GetObjectsByType<Comment>()) {
+ if (!addRowFn(comment, LivestatusGroupByNone, Empty))
+ return;
+ }
+}
+
+Object::Ptr CommentsTable::HostAccessor(const Value& row, const Column::ObjectAccessor&)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+
+ Checkable::Ptr checkable = comment->GetCheckable();
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ return host;
+}
+
+Object::Ptr CommentsTable::ServiceAccessor(const Value& row, const Column::ObjectAccessor&)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+
+ Checkable::Ptr checkable = comment->GetCheckable();
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ return service;
+}
+
+Value CommentsTable::AuthorAccessor(const Value& row)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+
+ if (!comment)
+ return Empty;
+
+ return comment->GetAuthor();
+}
+
+Value CommentsTable::CommentAccessor(const Value& row)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+
+ if (!comment)
+ return Empty;
+
+ return comment->GetText();
+}
+
+Value CommentsTable::IdAccessor(const Value& row)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+
+ if (!comment)
+ return Empty;
+
+ return comment->GetLegacyId();
+}
+
+Value CommentsTable::EntryTimeAccessor(const Value& row)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+
+ if (!comment)
+ return Empty;
+
+ return static_cast<int>(comment->GetEntryTime());
+}
+
+Value CommentsTable::TypeAccessor(const Value& row)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+ Checkable::Ptr checkable = comment->GetCheckable();
+
+ if (!checkable)
+ return Empty;
+
+ if (dynamic_pointer_cast<Host>(checkable))
+ return 1;
+ else
+ return 2;
+}
+
+Value CommentsTable::IsServiceAccessor(const Value& row)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+ Checkable::Ptr checkable = comment->GetCheckable();
+
+ if (!checkable)
+ return Empty;
+
+ return (dynamic_pointer_cast<Host>(checkable) ? 0 : 1);
+}
+
+Value CommentsTable::EntryTypeAccessor(const Value& row)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+
+ if (!comment)
+ return Empty;
+
+ return comment->GetEntryType();
+}
+
+Value CommentsTable::ExpiresAccessor(const Value& row)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+
+ if (!comment)
+ return Empty;
+
+ return comment->GetExpireTime() != 0;
+}
+
+Value CommentsTable::ExpireTimeAccessor(const Value& row)
+{
+ Comment::Ptr comment = static_cast<Comment::Ptr>(row);
+
+ if (!comment)
+ return Empty;
+
+ return static_cast<int>(comment->GetExpireTime());
+}
diff --git a/lib/livestatus/commentstable.hpp b/lib/livestatus/commentstable.hpp
new file mode 100644
index 0000000..b46e155
--- /dev/null
+++ b/lib/livestatus/commentstable.hpp
@@ -0,0 +1,49 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef COMMENTSTABLE_H
+#define COMMENTSTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class CommentsTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(CommentsTable);
+
+ CommentsTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+private:
+ static Object::Ptr HostAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+ static Object::Ptr ServiceAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+
+ static Value AuthorAccessor(const Value& row);
+ static Value CommentAccessor(const Value& row);
+ static Value IdAccessor(const Value& row);
+ static Value EntryTimeAccessor(const Value& row);
+ static Value TypeAccessor(const Value& row);
+ static Value IsServiceAccessor(const Value& row);
+ static Value EntryTypeAccessor(const Value& row);
+ static Value ExpiresAccessor(const Value& row);
+ static Value ExpireTimeAccessor(const Value& row);
+};
+
+}
+
+#endif /* COMMENTSTABLE_H */
diff --git a/lib/livestatus/contactgroupstable.cpp b/lib/livestatus/contactgroupstable.cpp
new file mode 100644
index 0000000..b4d6853
--- /dev/null
+++ b/lib/livestatus/contactgroupstable.cpp
@@ -0,0 +1,74 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/contactgroupstable.hpp"
+#include "icinga/usergroup.hpp"
+#include "base/configtype.hpp"
+
+using namespace icinga;
+
+ContactGroupsTable::ContactGroupsTable()
+{
+ AddColumns(this);
+}
+
+void ContactGroupsTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "name", Column(&ContactGroupsTable::NameAccessor, objectAccessor));
+ table->AddColumn(prefix + "alias", Column(&ContactGroupsTable::AliasAccessor, objectAccessor));
+ table->AddColumn(prefix + "members", Column(&ContactGroupsTable::MembersAccessor, objectAccessor));
+}
+
+String ContactGroupsTable::GetName() const
+{
+ return "contactgroups";
+}
+
+String ContactGroupsTable::GetPrefix() const
+{
+ return "contactgroup";
+}
+
+void ContactGroupsTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ for (const UserGroup::Ptr& ug : ConfigType::GetObjectsByType<UserGroup>()) {
+ if (!addRowFn(ug, LivestatusGroupByNone, Empty))
+ return;
+ }
+}
+
+Value ContactGroupsTable::NameAccessor(const Value& row)
+{
+ UserGroup::Ptr user_group = static_cast<UserGroup::Ptr>(row);
+
+ if (!user_group)
+ return Empty;
+
+ return user_group->GetName();
+}
+
+Value ContactGroupsTable::AliasAccessor(const Value& row)
+{
+ UserGroup::Ptr user_group = static_cast<UserGroup::Ptr>(row);
+
+ if (!user_group)
+ return Empty;
+
+ return user_group->GetName();
+}
+
+Value ContactGroupsTable::MembersAccessor(const Value& row)
+{
+ UserGroup::Ptr user_group = static_cast<UserGroup::Ptr>(row);
+
+ if (!user_group)
+ return Empty;
+
+ ArrayData result;
+
+ for (const User::Ptr& user : user_group->GetMembers()) {
+ result.push_back(user->GetName());
+ }
+
+ return new Array(std::move(result));
+}
diff --git a/lib/livestatus/contactgroupstable.hpp b/lib/livestatus/contactgroupstable.hpp
new file mode 100644
index 0000000..a57f5c3
--- /dev/null
+++ b/lib/livestatus/contactgroupstable.hpp
@@ -0,0 +1,39 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef CONTACTGROUPSTABLE_H
+#define CONTACTGROUPSTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class ContactGroupsTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ContactGroupsTable);
+
+ ContactGroupsTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Value NameAccessor(const Value& row);
+ static Value AliasAccessor(const Value& row);
+ static Value MembersAccessor(const Value& row);
+};
+
+}
+
+#endif /* CONTACTGROUPSTABLE_H */
diff --git a/lib/livestatus/contactstable.cpp b/lib/livestatus/contactstable.cpp
new file mode 100644
index 0000000..d6a04c4
--- /dev/null
+++ b/lib/livestatus/contactstable.cpp
@@ -0,0 +1,278 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/contactstable.hpp"
+#include "icinga/user.hpp"
+#include "icinga/timeperiod.hpp"
+#include "icinga/compatutility.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/json.hpp"
+#include "base/utility.hpp"
+
+using namespace icinga;
+
+ContactsTable::ContactsTable()
+{
+ AddColumns(this);
+}
+
+void ContactsTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "name", Column(&ContactsTable::NameAccessor, objectAccessor));
+ table->AddColumn(prefix + "alias", Column(&ContactsTable::NameAccessor, objectAccessor));
+ table->AddColumn(prefix + "email", Column(&ContactsTable::EmailAccessor, objectAccessor));
+ table->AddColumn(prefix + "pager", Column(&ContactsTable::PagerAccessor, objectAccessor));
+ table->AddColumn(prefix + "host_notification_period", Column(&ContactsTable::HostNotificationPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "service_notification_period", Column(&ContactsTable::ServiceNotificationPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "can_submit_commands", Column(&Table::OneAccessor, objectAccessor));
+ table->AddColumn(prefix + "host_notifications_enabled", Column(&ContactsTable::HostNotificationsEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "service_notifications_enabled", Column(&ContactsTable::ServiceNotificationsEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "in_host_notification_period", Column(&ContactsTable::InHostNotificationPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "in_service_notification_period", Column(&ContactsTable::InServiceNotificationPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "vars_variable_names", Column(&ContactsTable::CustomVariableNamesAccessor, objectAccessor));
+ table->AddColumn(prefix + "vars_variable_values", Column(&ContactsTable::CustomVariableValuesAccessor, objectAccessor));
+ table->AddColumn(prefix + "vars_variables", Column(&ContactsTable::CustomVariablesAccessor, objectAccessor));
+ table->AddColumn(prefix + "modified_attributes", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "modified_attributes_list", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "cv_is_json", Column(&ContactsTable::CVIsJsonAccessor, objectAccessor));
+
+}
+
+String ContactsTable::GetName() const
+{
+ return "contacts";
+}
+
+String ContactsTable::GetPrefix() const
+{
+ return "contact";
+}
+
+void ContactsTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ for (const User::Ptr& user : ConfigType::GetObjectsByType<User>()) {
+ if (!addRowFn(user, LivestatusGroupByNone, Empty))
+ return;
+ }
+}
+
+Value ContactsTable::NameAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ return user->GetName();
+}
+
+Value ContactsTable::AliasAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ return user->GetDisplayName();
+}
+
+Value ContactsTable::EmailAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ return user->GetEmail();
+}
+
+Value ContactsTable::PagerAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ return user->GetPager();
+}
+
+Value ContactsTable::HostNotificationPeriodAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ /* same as service */
+ TimePeriod::Ptr timeperiod = user->GetPeriod();
+
+ if (!timeperiod)
+ return Empty;
+
+ return timeperiod->GetName();
+}
+
+Value ContactsTable::ServiceNotificationPeriodAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ TimePeriod::Ptr timeperiod = user->GetPeriod();
+
+ if (!timeperiod)
+ return Empty;
+
+ return timeperiod->GetName();
+}
+
+Value ContactsTable::HostNotificationsEnabledAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ return (user->GetEnableNotifications() ? 1 : 0);
+}
+
+Value ContactsTable::ServiceNotificationsEnabledAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ return (user->GetEnableNotifications() ? 1 : 0);
+}
+
+Value ContactsTable::InHostNotificationPeriodAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ TimePeriod::Ptr timeperiod = user->GetPeriod();
+
+ if (!timeperiod)
+ return Empty;
+
+ return (timeperiod->IsInside(Utility::GetTime()) ? 1 : 0);
+}
+
+Value ContactsTable::InServiceNotificationPeriodAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ TimePeriod::Ptr timeperiod = user->GetPeriod();
+
+ if (!timeperiod)
+ return Empty;
+
+ return (timeperiod->IsInside(Utility::GetTime()) ? 1 : 0);
+}
+
+Value ContactsTable::CustomVariableNamesAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ Dictionary::Ptr vars = user->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ result.push_back(kv.first);
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ContactsTable::CustomVariableValuesAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ Dictionary::Ptr vars = user->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>())
+ result.push_back(JsonEncode(kv.second));
+ else
+ result.push_back(kv.second);
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ContactsTable::CustomVariablesAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ Dictionary::Ptr vars = user->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ Value val;
+
+ if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>())
+ val = JsonEncode(kv.second);
+ else
+ val = kv.second;
+
+ result.push_back(new Array({
+ kv.first,
+ val
+ }));
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ContactsTable::CVIsJsonAccessor(const Value& row)
+{
+ User::Ptr user = static_cast<User::Ptr>(row);
+
+ if (!user)
+ return Empty;
+
+ Dictionary::Ptr vars = user->GetVars();
+
+ if (!vars)
+ return Empty;
+
+ bool cv_is_json = false;
+
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>())
+ cv_is_json = true;
+ }
+
+ return cv_is_json;
+}
diff --git a/lib/livestatus/contactstable.hpp b/lib/livestatus/contactstable.hpp
new file mode 100644
index 0000000..0bd2679
--- /dev/null
+++ b/lib/livestatus/contactstable.hpp
@@ -0,0 +1,50 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef CONTACTSTABLE_H
+#define CONTACTSTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class ContactsTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ContactsTable);
+
+ ContactsTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Value NameAccessor(const Value& row);
+ static Value AliasAccessor(const Value& row);
+ static Value EmailAccessor(const Value& row);
+ static Value PagerAccessor(const Value& row);
+ static Value HostNotificationPeriodAccessor(const Value& row);
+ static Value ServiceNotificationPeriodAccessor(const Value& row);
+ static Value HostNotificationsEnabledAccessor(const Value& row);
+ static Value ServiceNotificationsEnabledAccessor(const Value& row);
+ static Value InHostNotificationPeriodAccessor(const Value& row);
+ static Value InServiceNotificationPeriodAccessor(const Value& row);
+ static Value CustomVariableNamesAccessor(const Value& row);
+ static Value CustomVariableValuesAccessor(const Value& row);
+ static Value CustomVariablesAccessor(const Value& row);
+ static Value CVIsJsonAccessor(const Value& row);
+};
+
+}
+
+#endif /* CONTACTSTABLE_H */
diff --git a/lib/livestatus/countaggregator.cpp b/lib/livestatus/countaggregator.cpp
new file mode 100644
index 0000000..b8a7238
--- /dev/null
+++ b/lib/livestatus/countaggregator.cpp
@@ -0,0 +1,30 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/countaggregator.hpp"
+
+using namespace icinga;
+
+CountAggregatorState *CountAggregator::EnsureState(AggregatorState **state)
+{
+ if (!*state)
+ *state = new CountAggregatorState();
+
+ return static_cast<CountAggregatorState *>(*state);
+}
+
+void CountAggregator::Apply(const Table::Ptr& table, const Value& row, AggregatorState **state)
+{
+ CountAggregatorState *pstate = EnsureState(state);
+
+ if (GetFilter()->Apply(table, row))
+ pstate->Count++;
+}
+
+double CountAggregator::GetResultAndFreeState(AggregatorState *state) const
+{
+ CountAggregatorState *pstate = EnsureState(&state);
+ double result = pstate->Count;
+ delete pstate;
+
+ return result;
+}
diff --git a/lib/livestatus/countaggregator.hpp b/lib/livestatus/countaggregator.hpp
new file mode 100644
index 0000000..22d4983
--- /dev/null
+++ b/lib/livestatus/countaggregator.hpp
@@ -0,0 +1,37 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef COUNTAGGREGATOR_H
+#define COUNTAGGREGATOR_H
+
+#include "livestatus/table.hpp"
+#include "livestatus/aggregator.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+struct CountAggregatorState final : public AggregatorState
+{
+ int Count{0};
+};
+
+/**
+ * @ingroup livestatus
+ */
+class CountAggregator final : public Aggregator
+{
+public:
+ DECLARE_PTR_TYPEDEFS(CountAggregator);
+
+ void Apply(const Table::Ptr& table, const Value& row, AggregatorState **) override;
+ double GetResultAndFreeState(AggregatorState *state) const override;
+
+private:
+ static CountAggregatorState *EnsureState(AggregatorState **state);
+};
+
+}
+
+#endif /* COUNTAGGREGATOR_H */
diff --git a/lib/livestatus/downtimestable.cpp b/lib/livestatus/downtimestable.cpp
new file mode 100644
index 0000000..09c111e
--- /dev/null
+++ b/lib/livestatus/downtimestable.cpp
@@ -0,0 +1,168 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/downtimestable.hpp"
+#include "livestatus/hoststable.hpp"
+#include "livestatus/servicestable.hpp"
+#include "icinga/service.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+
+using namespace icinga;
+
+DowntimesTable::DowntimesTable()
+{
+ AddColumns(this);
+}
+
+void DowntimesTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "author", Column(&DowntimesTable::AuthorAccessor, objectAccessor));
+ table->AddColumn(prefix + "comment", Column(&DowntimesTable::CommentAccessor, objectAccessor));
+ table->AddColumn(prefix + "id", Column(&DowntimesTable::IdAccessor, objectAccessor));
+ table->AddColumn(prefix + "entry_time", Column(&DowntimesTable::EntryTimeAccessor, objectAccessor));
+ table->AddColumn(prefix + "type", Column(&DowntimesTable::TypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "is_service", Column(&DowntimesTable::IsServiceAccessor, objectAccessor));
+ table->AddColumn(prefix + "start_time", Column(&DowntimesTable::StartTimeAccessor, objectAccessor));
+ table->AddColumn(prefix + "end_time", Column(&DowntimesTable::EndTimeAccessor, objectAccessor));
+ table->AddColumn(prefix + "fixed", Column(&DowntimesTable::FixedAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration", Column(&DowntimesTable::DurationAccessor, objectAccessor));
+ table->AddColumn(prefix + "triggered_by", Column(&DowntimesTable::TriggeredByAccessor, objectAccessor));
+
+ /* order is important - host w/o services must not be empty */
+ ServicesTable::AddColumns(table, "service_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return ServiceAccessor(row, objectAccessor);
+ });
+ HostsTable::AddColumns(table, "host_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return HostAccessor(row, objectAccessor);
+ });
+}
+
+String DowntimesTable::GetName() const
+{
+ return "downtimes";
+}
+
+String DowntimesTable::GetPrefix() const
+{
+ return "downtime";
+}
+
+void DowntimesTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
+ if (!addRowFn(downtime, LivestatusGroupByNone, Empty))
+ return;
+ }
+}
+
+Object::Ptr DowntimesTable::HostAccessor(const Value& row, const Column::ObjectAccessor&)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ Checkable::Ptr checkable = downtime->GetCheckable();
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ return host;
+}
+
+Object::Ptr DowntimesTable::ServiceAccessor(const Value& row, const Column::ObjectAccessor&)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ Checkable::Ptr checkable = downtime->GetCheckable();
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ return service;
+}
+
+Value DowntimesTable::AuthorAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ return downtime->GetAuthor();
+}
+
+Value DowntimesTable::CommentAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ return downtime->GetComment();
+}
+
+Value DowntimesTable::IdAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ return downtime->GetLegacyId();
+}
+
+Value DowntimesTable::EntryTimeAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ return static_cast<int>(downtime->GetEntryTime());
+}
+
+Value DowntimesTable::TypeAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+ // 1 .. active, 0 .. pending
+ return (downtime->IsInEffect() ? 1 : 0);
+}
+
+Value DowntimesTable::IsServiceAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+ Checkable::Ptr checkable = downtime->GetCheckable();
+
+ return (dynamic_pointer_cast<Host>(checkable) ? 0 : 1);
+}
+
+Value DowntimesTable::StartTimeAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ return static_cast<int>(downtime->GetStartTime());
+}
+
+Value DowntimesTable::EndTimeAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ return static_cast<int>(downtime->GetEndTime());
+}
+
+Value DowntimesTable::FixedAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ return downtime->GetFixed();
+}
+
+Value DowntimesTable::DurationAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ return downtime->GetDuration();
+}
+
+Value DowntimesTable::TriggeredByAccessor(const Value& row)
+{
+ Downtime::Ptr downtime = static_cast<Downtime::Ptr>(row);
+
+ String triggerDowntimeName = downtime->GetTriggeredBy();
+
+ Downtime::Ptr triggerDowntime = Downtime::GetByName(triggerDowntimeName);
+
+ if (triggerDowntime)
+ return triggerDowntime->GetLegacyId();
+
+ return Empty;
+}
diff --git a/lib/livestatus/downtimestable.hpp b/lib/livestatus/downtimestable.hpp
new file mode 100644
index 0000000..4b5c909
--- /dev/null
+++ b/lib/livestatus/downtimestable.hpp
@@ -0,0 +1,51 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef DOWNTIMESTABLE_H
+#define DOWNTIMESTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class DowntimesTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(DowntimesTable);
+
+ DowntimesTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+private:
+ static Object::Ptr HostAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+ static Object::Ptr ServiceAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+
+ static Value AuthorAccessor(const Value& row);
+ static Value CommentAccessor(const Value& row);
+ static Value IdAccessor(const Value& row);
+ static Value EntryTimeAccessor(const Value& row);
+ static Value TypeAccessor(const Value& row);
+ static Value IsServiceAccessor(const Value& row);
+ static Value StartTimeAccessor(const Value& row);
+ static Value EndTimeAccessor(const Value& row);
+ static Value FixedAccessor(const Value& row);
+ static Value DurationAccessor(const Value& row);
+ static Value TriggeredByAccessor(const Value& row);
+};
+
+}
+
+#endif /* DOWNTIMESTABLE_H */
diff --git a/lib/livestatus/endpointstable.cpp b/lib/livestatus/endpointstable.cpp
new file mode 100644
index 0000000..3d407eb
--- /dev/null
+++ b/lib/livestatus/endpointstable.cpp
@@ -0,0 +1,109 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/endpointstable.hpp"
+#include "icinga/host.hpp"
+#include "icinga/service.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "remote/endpoint.hpp"
+#include "remote/zone.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/convert.hpp"
+#include "base/utility.hpp"
+#include <boost/algorithm/string/replace.hpp>
+
+using namespace icinga;
+
+EndpointsTable::EndpointsTable()
+{
+ AddColumns(this);
+}
+
+void EndpointsTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "name", Column(&EndpointsTable::NameAccessor, objectAccessor));
+ table->AddColumn(prefix + "identity", Column(&EndpointsTable::IdentityAccessor, objectAccessor));
+ table->AddColumn(prefix + "node", Column(&EndpointsTable::NodeAccessor, objectAccessor));
+ table->AddColumn(prefix + "is_connected", Column(&EndpointsTable::IsConnectedAccessor, objectAccessor));
+ table->AddColumn(prefix + "zone", Column(&EndpointsTable::ZoneAccessor, objectAccessor));
+}
+
+String EndpointsTable::GetName() const
+{
+ return "endpoints";
+}
+
+String EndpointsTable::GetPrefix() const
+{
+ return "endpoint";
+}
+
+void EndpointsTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ for (const Endpoint::Ptr& endpoint : ConfigType::GetObjectsByType<Endpoint>()) {
+ if (!addRowFn(endpoint, LivestatusGroupByNone, Empty))
+ return;
+ }
+}
+
+Value EndpointsTable::NameAccessor(const Value& row)
+{
+ Endpoint::Ptr endpoint = static_cast<Endpoint::Ptr>(row);
+
+ if (!endpoint)
+ return Empty;
+
+ return endpoint->GetName();
+}
+
+Value EndpointsTable::IdentityAccessor(const Value& row)
+{
+ Endpoint::Ptr endpoint = static_cast<Endpoint::Ptr>(row);
+
+ if (!endpoint)
+ return Empty;
+
+ return endpoint->GetName();
+}
+
+Value EndpointsTable::NodeAccessor(const Value& row)
+{
+ Endpoint::Ptr endpoint = static_cast<Endpoint::Ptr>(row);
+
+ if (!endpoint)
+ return Empty;
+
+ return IcingaApplication::GetInstance()->GetNodeName();
+}
+
+Value EndpointsTable::IsConnectedAccessor(const Value& row)
+{
+ Endpoint::Ptr endpoint = static_cast<Endpoint::Ptr>(row);
+
+ if (!endpoint)
+ return Empty;
+
+ unsigned int is_connected = endpoint->GetConnected() ? 1 : 0;
+
+ /* if identity is equal to node, fake is_connected */
+ if (endpoint->GetName() == IcingaApplication::GetInstance()->GetNodeName())
+ is_connected = 1;
+
+ return is_connected;
+}
+
+Value EndpointsTable::ZoneAccessor(const Value& row)
+{
+ Endpoint::Ptr endpoint = static_cast<Endpoint::Ptr>(row);
+
+ if (!endpoint)
+ return Empty;
+
+ Zone::Ptr zone = endpoint->GetZone();
+
+ if (!zone)
+ return Empty;
+
+ return zone->GetName();
+}
diff --git a/lib/livestatus/endpointstable.hpp b/lib/livestatus/endpointstable.hpp
new file mode 100644
index 0000000..7d011ef
--- /dev/null
+++ b/lib/livestatus/endpointstable.hpp
@@ -0,0 +1,41 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef ENDPOINTSTABLE_H
+#define ENDPOINTSTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class EndpointsTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(EndpointsTable);
+
+ EndpointsTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Value NameAccessor(const Value& row);
+ static Value IdentityAccessor(const Value& row);
+ static Value NodeAccessor(const Value& row);
+ static Value IsConnectedAccessor(const Value& row);
+ static Value ZoneAccessor(const Value& row);
+};
+
+}
+
+#endif /* ENDPOINTSTABLE_H */
diff --git a/lib/livestatus/filter.hpp b/lib/livestatus/filter.hpp
new file mode 100644
index 0000000..b9a01c8
--- /dev/null
+++ b/lib/livestatus/filter.hpp
@@ -0,0 +1,28 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef FILTER_H
+#define FILTER_H
+
+#include "livestatus/i2-livestatus.hpp"
+#include "livestatus/table.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class Filter : public Object
+{
+public:
+ DECLARE_PTR_TYPEDEFS(Filter);
+
+ virtual bool Apply(const Table::Ptr& table, const Value& row) = 0;
+
+protected:
+ Filter() = default;
+};
+
+}
+
+#endif /* FILTER_H */
diff --git a/lib/livestatus/historytable.hpp b/lib/livestatus/historytable.hpp
new file mode 100644
index 0000000..f117857
--- /dev/null
+++ b/lib/livestatus/historytable.hpp
@@ -0,0 +1,24 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef HISTORYTABLE_H
+#define HISTORYTABLE_H
+
+#include "livestatus/table.hpp"
+#include "base/dictionary.hpp"
+
+namespace icinga
+{
+
+
+/**
+ * @ingroup livestatus
+ */
+class HistoryTable : public Table
+{
+public:
+ virtual void UpdateLogEntries(const Dictionary::Ptr& bag, int line_count, int lineno, const AddRowFunction& addRowFn) = 0;
+};
+
+}
+
+#endif /* HISTORYTABLE_H */
diff --git a/lib/livestatus/hostgroupstable.cpp b/lib/livestatus/hostgroupstable.cpp
new file mode 100644
index 0000000..984eddb
--- /dev/null
+++ b/lib/livestatus/hostgroupstable.cpp
@@ -0,0 +1,473 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/hostgroupstable.hpp"
+#include "icinga/hostgroup.hpp"
+#include "icinga/host.hpp"
+#include "icinga/service.hpp"
+#include "base/configtype.hpp"
+
+using namespace icinga;
+
+HostGroupsTable::HostGroupsTable()
+{
+ AddColumns(this);
+}
+
+void HostGroupsTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "name", Column(&HostGroupsTable::NameAccessor, objectAccessor));
+ table->AddColumn(prefix + "alias", Column(&HostGroupsTable::AliasAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes", Column(&HostGroupsTable::NotesAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes_url", Column(&HostGroupsTable::NotesUrlAccessor, objectAccessor));
+ table->AddColumn(prefix + "action_url", Column(&HostGroupsTable::ActionUrlAccessor, objectAccessor));
+ table->AddColumn(prefix + "members", Column(&HostGroupsTable::MembersAccessor, objectAccessor));
+ table->AddColumn(prefix + "members_with_state", Column(&HostGroupsTable::MembersWithStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "worst_host_state", Column(&HostGroupsTable::WorstHostStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_hosts", Column(&HostGroupsTable::NumHostsAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_hosts_pending", Column(&HostGroupsTable::NumHostsPendingAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_hosts_up", Column(&HostGroupsTable::NumHostsUpAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_hosts_down", Column(&HostGroupsTable::NumHostsDownAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_hosts_unreach", Column(&HostGroupsTable::NumHostsUnreachAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services", Column(&HostGroupsTable::NumServicesAccessor, objectAccessor));
+ table->AddColumn(prefix + "worst_service_state", Column(&HostGroupsTable::WorstServiceStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_pending", Column(&HostGroupsTable::NumServicesPendingAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_ok", Column(&HostGroupsTable::NumServicesOkAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_warn", Column(&HostGroupsTable::NumServicesWarnAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_crit", Column(&HostGroupsTable::NumServicesCritAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_unknown", Column(&HostGroupsTable::NumServicesUnknownAccessor, objectAccessor));
+ table->AddColumn(prefix + "worst_service_hard_state", Column(&HostGroupsTable::WorstServiceHardStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_ok", Column(&HostGroupsTable::NumServicesHardOkAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_warn", Column(&HostGroupsTable::NumServicesHardWarnAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_crit", Column(&HostGroupsTable::NumServicesHardCritAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_unknown", Column(&HostGroupsTable::NumServicesHardUnknownAccessor, objectAccessor));
+}
+
+String HostGroupsTable::GetName() const
+{
+ return "hostgroups";
+}
+
+String HostGroupsTable::GetPrefix() const
+{
+ return "hostgroup";
+}
+
+void HostGroupsTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ for (const HostGroup::Ptr& hg : ConfigType::GetObjectsByType<HostGroup>()) {
+ if (!addRowFn(hg, LivestatusGroupByNone, Empty))
+ return;
+ }
+}
+
+Value HostGroupsTable::NameAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ return hg->GetName();
+}
+
+Value HostGroupsTable::AliasAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ return hg->GetDisplayName();
+}
+
+Value HostGroupsTable::NotesAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ return hg->GetNotes();
+}
+
+Value HostGroupsTable::NotesUrlAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ return hg->GetNotesUrl();
+}
+
+Value HostGroupsTable::ActionUrlAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ return hg->GetActionUrl();
+}
+
+Value HostGroupsTable::MembersAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ ArrayData members;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ members.push_back(host->GetName());
+ }
+
+ return new Array(std::move(members));
+}
+
+Value HostGroupsTable::MembersWithStateAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ ArrayData members;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ members.push_back(new Array({
+ host->GetName(),
+ host->GetState()
+ }));
+ }
+
+ return new Array(std::move(members));
+}
+
+Value HostGroupsTable::WorstHostStateAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int worst_host = HostUp;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ if (host->GetState() > worst_host)
+ worst_host = host->GetState();
+ }
+
+ return worst_host;
+}
+
+Value HostGroupsTable::NumHostsAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ return hg->GetMembers().size();
+}
+
+Value HostGroupsTable::NumHostsPendingAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_hosts = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ /* no checkresult */
+ if (!host->GetLastCheckResult())
+ num_hosts++;
+ }
+
+ return num_hosts;
+}
+
+Value HostGroupsTable::NumHostsUpAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_hosts = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ if (host->GetState() == HostUp)
+ num_hosts++;
+ }
+
+ return num_hosts;
+}
+
+Value HostGroupsTable::NumHostsDownAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_hosts = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ if (host->GetState() == HostDown)
+ num_hosts++;
+ }
+
+ return num_hosts;
+}
+
+Value HostGroupsTable::NumHostsUnreachAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_hosts = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ if (!host->IsReachable())
+ num_hosts++;
+ }
+
+ return num_hosts;
+}
+
+Value HostGroupsTable::NumServicesAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_services = 0;
+
+ if (hg->GetMembers().size() == 0)
+ return 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ num_services += host->GetServices().size();
+ }
+
+ return num_services;
+}
+
+Value HostGroupsTable::WorstServiceStateAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ Value worst_service = ServiceOK;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetState() > worst_service)
+ worst_service = service->GetState();
+ }
+ }
+
+ return worst_service;
+}
+
+Value HostGroupsTable::NumServicesPendingAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (!service->GetLastCheckResult())
+ num_services++;
+ }
+ }
+
+ return num_services;
+}
+
+Value HostGroupsTable::NumServicesOkAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetState() == ServiceOK)
+ num_services++;
+ }
+ }
+
+ return num_services;
+}
+
+Value HostGroupsTable::NumServicesWarnAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetState() == ServiceWarning)
+ num_services++;
+ }
+ }
+
+ return num_services;
+}
+
+Value HostGroupsTable::NumServicesCritAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetState() == ServiceCritical)
+ num_services++;
+ }
+ }
+
+ return num_services;
+}
+
+Value HostGroupsTable::NumServicesUnknownAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetState() == ServiceUnknown)
+ num_services++;
+ }
+ }
+
+ return num_services;
+}
+
+Value HostGroupsTable::WorstServiceHardStateAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ Value worst_service = ServiceOK;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetStateType() == StateTypeHard) {
+ if (service->GetState() > worst_service)
+ worst_service = service->GetState();
+ }
+ }
+ }
+
+ return worst_service;
+}
+
+Value HostGroupsTable::NumServicesHardOkAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceOK)
+ num_services++;
+ }
+ }
+
+ return num_services;
+}
+
+Value HostGroupsTable::NumServicesHardWarnAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceWarning)
+ num_services++;
+ }
+ }
+
+ return num_services;
+}
+
+Value HostGroupsTable::NumServicesHardCritAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceCritical)
+ num_services++;
+ }
+ }
+
+ return num_services;
+}
+
+Value HostGroupsTable::NumServicesHardUnknownAccessor(const Value& row)
+{
+ HostGroup::Ptr hg = static_cast<HostGroup::Ptr>(row);
+
+ if (!hg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceUnknown)
+ num_services++;
+ }
+ }
+
+ return num_services;
+}
diff --git a/lib/livestatus/hostgroupstable.hpp b/lib/livestatus/hostgroupstable.hpp
new file mode 100644
index 0000000..cc5039f
--- /dev/null
+++ b/lib/livestatus/hostgroupstable.hpp
@@ -0,0 +1,61 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef HOSTGROUPSTABLE_H
+#define HOSTGROUPSTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class HostGroupsTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(HostGroupsTable);
+
+ HostGroupsTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Value NameAccessor(const Value& row);
+ static Value AliasAccessor(const Value& row);
+ static Value NotesAccessor(const Value& row);
+ static Value NotesUrlAccessor(const Value& row);
+ static Value ActionUrlAccessor(const Value& row);
+ static Value MembersAccessor(const Value& row);
+ static Value MembersWithStateAccessor(const Value& row);
+ static Value WorstHostStateAccessor(const Value& row);
+ static Value NumHostsAccessor(const Value& row);
+ static Value NumHostsPendingAccessor(const Value& row);
+ static Value NumHostsUpAccessor(const Value& row);
+ static Value NumHostsDownAccessor(const Value& row);
+ static Value NumHostsUnreachAccessor(const Value& row);
+ static Value NumServicesAccessor(const Value& row);
+ static Value WorstServiceStateAccessor(const Value& row);
+ static Value NumServicesPendingAccessor(const Value& row);
+ static Value NumServicesOkAccessor(const Value& row);
+ static Value NumServicesWarnAccessor(const Value& row);
+ static Value NumServicesCritAccessor(const Value& row);
+ static Value NumServicesUnknownAccessor(const Value& row);
+ static Value WorstServiceHardStateAccessor(const Value& row);
+ static Value NumServicesHardOkAccessor(const Value& row);
+ static Value NumServicesHardWarnAccessor(const Value& row);
+ static Value NumServicesHardCritAccessor(const Value& row);
+ static Value NumServicesHardUnknownAccessor(const Value& row);
+};
+
+}
+
+#endif /* HOSTGROUPSTABLE_H */
diff --git a/lib/livestatus/hoststable.cpp b/lib/livestatus/hoststable.cpp
new file mode 100644
index 0000000..d90f4a5
--- /dev/null
+++ b/lib/livestatus/hoststable.cpp
@@ -0,0 +1,1517 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/hoststable.hpp"
+#include "livestatus/hostgroupstable.hpp"
+#include "livestatus/endpointstable.hpp"
+#include "icinga/host.hpp"
+#include "icinga/service.hpp"
+#include "icinga/hostgroup.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/timeperiod.hpp"
+#include "icinga/macroprocessor.hpp"
+#include "icinga/compatutility.hpp"
+#include "icinga/pluginutility.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/json.hpp"
+#include "base/convert.hpp"
+#include "base/utility.hpp"
+#include <boost/algorithm/string/replace.hpp>
+
+using namespace icinga;
+
+HostsTable::HostsTable(LivestatusGroupByType type)
+ :Table(type)
+{
+ AddColumns(this);
+}
+
+void HostsTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "name", Column(&HostsTable::NameAccessor, objectAccessor));
+ table->AddColumn(prefix + "host_name", Column(&HostsTable::NameAccessor, objectAccessor)); //ugly compatibility hack
+ table->AddColumn(prefix + "display_name", Column(&HostsTable::DisplayNameAccessor, objectAccessor));
+ table->AddColumn(prefix + "alias", Column(&HostsTable::DisplayNameAccessor, objectAccessor));
+ table->AddColumn(prefix + "address", Column(&HostsTable::AddressAccessor, objectAccessor));
+ table->AddColumn(prefix + "address6", Column(&HostsTable::Address6Accessor, objectAccessor));
+ table->AddColumn(prefix + "check_command", Column(&HostsTable::CheckCommandAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_command_expanded", Column(&HostsTable::CheckCommandExpandedAccessor, objectAccessor));
+ table->AddColumn(prefix + "event_handler", Column(&HostsTable::EventHandlerAccessor, objectAccessor));
+ table->AddColumn(prefix + "notification_period", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_period", Column(&HostsTable::CheckPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes", Column(&HostsTable::NotesAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes_expanded", Column(&HostsTable::NotesExpandedAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes_url", Column(&HostsTable::NotesUrlAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes_url_expanded", Column(&HostsTable::NotesUrlExpandedAccessor, objectAccessor));
+ table->AddColumn(prefix + "action_url", Column(&HostsTable::ActionUrlAccessor, objectAccessor));
+ table->AddColumn(prefix + "action_url_expanded", Column(&HostsTable::ActionUrlExpandedAccessor, objectAccessor));
+ table->AddColumn(prefix + "plugin_output", Column(&HostsTable::PluginOutputAccessor, objectAccessor));
+ table->AddColumn(prefix + "perf_data", Column(&HostsTable::PerfDataAccessor, objectAccessor));
+ table->AddColumn(prefix + "icon_image", Column(&HostsTable::IconImageAccessor, objectAccessor));
+ table->AddColumn(prefix + "icon_image_expanded", Column(&HostsTable::IconImageExpandedAccessor, objectAccessor));
+ table->AddColumn(prefix + "icon_image_alt", Column(&HostsTable::IconImageAltAccessor, objectAccessor));
+ table->AddColumn(prefix + "statusmap_image", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "long_plugin_output", Column(&HostsTable::LongPluginOutputAccessor, objectAccessor));
+ table->AddColumn(prefix + "initial_state", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "max_check_attempts", Column(&HostsTable::MaxCheckAttemptsAccessor, objectAccessor));
+ table->AddColumn(prefix + "flap_detection_enabled", Column(&HostsTable::FlapDetectionEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_freshness", Column(&Table::OneAccessor, objectAccessor));
+ table->AddColumn(prefix + "process_performance_data", Column(&HostsTable::ProcessPerformanceDataAccessor, objectAccessor));
+ table->AddColumn(prefix + "accept_passive_checks", Column(&HostsTable::AcceptPassiveChecksAccessor, objectAccessor));
+ table->AddColumn(prefix + "event_handler_enabled", Column(&HostsTable::EventHandlerEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "acknowledgement_type", Column(&HostsTable::AcknowledgementTypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_type", Column(&HostsTable::CheckTypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_state", Column(&HostsTable::LastStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_hard_state", Column(&HostsTable::LastHardStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "current_attempt", Column(&HostsTable::CurrentAttemptAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_notification", Column(&HostsTable::LastNotificationAccessor, objectAccessor));
+ table->AddColumn(prefix + "next_notification", Column(&HostsTable::NextNotificationAccessor, objectAccessor));
+ table->AddColumn(prefix + "next_check", Column(&HostsTable::NextCheckAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_hard_state_change", Column(&HostsTable::LastHardStateChangeAccessor, objectAccessor));
+ table->AddColumn(prefix + "has_been_checked", Column(&HostsTable::HasBeenCheckedAccessor, objectAccessor));
+ table->AddColumn(prefix + "current_notification_number", Column(&HostsTable::CurrentNotificationNumberAccessor, objectAccessor));
+ table->AddColumn(prefix + "pending_flex_downtime", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "total_services", Column(&HostsTable::TotalServicesAccessor, objectAccessor));
+ table->AddColumn(prefix + "checks_enabled", Column(&HostsTable::ChecksEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "notifications_enabled", Column(&HostsTable::NotificationsEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "acknowledged", Column(&HostsTable::AcknowledgedAccessor, objectAccessor));
+ table->AddColumn(prefix + "state", Column(&HostsTable::StateAccessor, objectAccessor));
+ table->AddColumn(prefix + "state_type", Column(&HostsTable::StateTypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "no_more_notifications", Column(&HostsTable::NoMoreNotificationsAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_flapping_recovery_notification", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_check", Column(&HostsTable::LastCheckAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_state_change", Column(&HostsTable::LastStateChangeAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_time_up", Column(&HostsTable::LastTimeUpAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_time_down", Column(&HostsTable::LastTimeDownAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_time_unreachable", Column(&HostsTable::LastTimeUnreachableAccessor, objectAccessor));
+ table->AddColumn(prefix + "is_flapping", Column(&HostsTable::IsFlappingAccessor, objectAccessor));
+ table->AddColumn(prefix + "scheduled_downtime_depth", Column(&HostsTable::ScheduledDowntimeDepthAccessor, objectAccessor));
+ table->AddColumn(prefix + "is_executing", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "active_checks_enabled", Column(&HostsTable::ActiveChecksEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_options", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "obsess_over_host", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "modified_attributes", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "modified_attributes_list", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_interval", Column(&HostsTable::CheckIntervalAccessor, objectAccessor));
+ table->AddColumn(prefix + "retry_interval", Column(&HostsTable::RetryIntervalAccessor, objectAccessor));
+ table->AddColumn(prefix + "notification_interval", Column(&HostsTable::NotificationIntervalAccessor, objectAccessor));
+ table->AddColumn(prefix + "first_notification_delay", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "low_flap_threshold", Column(&HostsTable::LowFlapThresholdAccessor, objectAccessor));
+ table->AddColumn(prefix + "high_flap_threshold", Column(&HostsTable::HighFlapThresholdAccessor, objectAccessor));
+ table->AddColumn(prefix + "x_3d", Column(&EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "y_3d", Column(&EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "z_3d", Column(&EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "x_2d", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "y_2d", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "latency", Column(&HostsTable::LatencyAccessor, objectAccessor));
+ table->AddColumn(prefix + "execution_time", Column(&HostsTable::ExecutionTimeAccessor, objectAccessor));
+ table->AddColumn(prefix + "percent_state_change", Column(&HostsTable::PercentStateChangeAccessor, objectAccessor));
+ table->AddColumn(prefix + "in_notification_period", Column(&HostsTable::InNotificationPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "in_check_period", Column(&HostsTable::InCheckPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "contacts", Column(&HostsTable::ContactsAccessor, objectAccessor));
+ table->AddColumn(prefix + "downtimes", Column(&HostsTable::DowntimesAccessor, objectAccessor));
+ table->AddColumn(prefix + "downtimes_with_info", Column(&HostsTable::DowntimesWithInfoAccessor, objectAccessor));
+ table->AddColumn(prefix + "comments", Column(&HostsTable::CommentsAccessor, objectAccessor));
+ table->AddColumn(prefix + "comments_with_info", Column(&HostsTable::CommentsWithInfoAccessor, objectAccessor));
+ table->AddColumn(prefix + "comments_with_extra_info", Column(&HostsTable::CommentsWithExtraInfoAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variable_names", Column(&HostsTable::CustomVariableNamesAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variable_values", Column(&HostsTable::CustomVariableValuesAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variables", Column(&HostsTable::CustomVariablesAccessor, objectAccessor));
+ table->AddColumn(prefix + "filename", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "parents", Column(&HostsTable::ParentsAccessor, objectAccessor));
+ table->AddColumn(prefix + "childs", Column(&HostsTable::ChildsAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services", Column(&HostsTable::NumServicesAccessor, objectAccessor));
+ table->AddColumn(prefix + "worst_service_state", Column(&HostsTable::WorstServiceStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_ok", Column(&HostsTable::NumServicesOkAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_warn", Column(&HostsTable::NumServicesWarnAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_crit", Column(&HostsTable::NumServicesCritAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_unknown", Column(&HostsTable::NumServicesUnknownAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_pending", Column(&HostsTable::NumServicesPendingAccessor, objectAccessor));
+ table->AddColumn(prefix + "worst_service_hard_state", Column(&HostsTable::WorstServiceHardStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_ok", Column(&HostsTable::NumServicesHardOkAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_warn", Column(&HostsTable::NumServicesHardWarnAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_crit", Column(&HostsTable::NumServicesHardCritAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_unknown", Column(&HostsTable::NumServicesHardUnknownAccessor, objectAccessor));
+ table->AddColumn(prefix + "hard_state", Column(&HostsTable::HardStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "pnpgraph_present", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "staleness", Column(&HostsTable::StalenessAccessor, objectAccessor));
+ table->AddColumn(prefix + "groups", Column(&HostsTable::GroupsAccessor, objectAccessor));
+ table->AddColumn(prefix + "contact_groups", Column(&HostsTable::ContactGroupsAccessor, objectAccessor));
+ table->AddColumn(prefix + "services", Column(&HostsTable::ServicesAccessor, objectAccessor));
+ table->AddColumn(prefix + "services_with_state", Column(&HostsTable::ServicesWithStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "services_with_info", Column(&HostsTable::ServicesWithInfoAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_source", Column(&HostsTable::CheckSourceAccessor, objectAccessor));
+ table->AddColumn(prefix + "is_reachable", Column(&HostsTable::IsReachableAccessor, objectAccessor));
+ table->AddColumn(prefix + "cv_is_json", Column(&HostsTable::CVIsJsonAccessor, objectAccessor));
+ table->AddColumn(prefix + "original_attributes", Column(&HostsTable::OriginalAttributesAccessor, objectAccessor));
+
+ /* add additional group by values received through the object accessor */
+ if (table->GetGroupByType() == LivestatusGroupByHostGroup) {
+ /* _1 = row, _2 = groupByType, _3 = groupByObject */
+ Log(LogDebug, "Livestatus")
+ << "Processing hosts group by hostgroup table.";
+ HostGroupsTable::AddColumns(table, "hostgroup_", [](const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject) -> Value {
+ return HostGroupAccessor(row, groupByType, groupByObject);
+ });
+ }
+}
+
+String HostsTable::GetName() const
+{
+ return "hosts";
+}
+
+String HostsTable::GetPrefix() const
+{
+ return "host";
+}
+
+void HostsTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ if (GetGroupByType() == LivestatusGroupByHostGroup) {
+ for (const HostGroup::Ptr& hg : ConfigType::GetObjectsByType<HostGroup>()) {
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ /* the caller must know which groupby type and value are set for this row */
+ if (!addRowFn(host, LivestatusGroupByHostGroup, hg))
+ return;
+ }
+ }
+ } else {
+ for (const Host::Ptr& host : ConfigType::GetObjectsByType<Host>()) {
+ if (!addRowFn(host, LivestatusGroupByNone, Empty))
+ return;
+ }
+ }
+}
+
+Object::Ptr HostsTable::HostGroupAccessor(const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject)
+{
+ /* return the current group by value set from within FetchRows()
+ * this is the hostgrouo object used for the table join inside
+ * in AddColumns()
+ */
+ if (groupByType == LivestatusGroupByHostGroup)
+ return groupByObject;
+
+ return nullptr;
+}
+
+Value HostsTable::NameAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetName();
+}
+
+Value HostsTable::DisplayNameAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetDisplayName();
+}
+
+Value HostsTable::AddressAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetAddress();
+}
+
+Value HostsTable::Address6Accessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetAddress6();
+}
+
+Value HostsTable::CheckCommandAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ CheckCommand::Ptr checkcommand = host->GetCheckCommand();
+ if (checkcommand)
+ return CompatUtility::GetCommandName(checkcommand) + "!" + CompatUtility::GetCheckableCommandArgs(host);
+
+ return Empty;
+}
+
+Value HostsTable::CheckCommandExpandedAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ CheckCommand::Ptr checkcommand = host->GetCheckCommand();
+ if (checkcommand)
+ return CompatUtility::GetCommandName(checkcommand) + "!" + CompatUtility::GetCheckableCommandArgs(host);
+
+ return Empty;
+}
+
+Value HostsTable::EventHandlerAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ EventCommand::Ptr eventcommand = host->GetEventCommand();
+ if (eventcommand)
+ return CompatUtility::GetCommandName(eventcommand);
+
+ return Empty;
+}
+
+Value HostsTable::CheckPeriodAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ TimePeriod::Ptr checkPeriod = host->GetCheckPeriod();
+
+ if (!checkPeriod)
+ return Empty;
+
+ return checkPeriod->GetName();
+}
+
+Value HostsTable::NotesAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetNotes();
+}
+
+Value HostsTable::NotesExpandedAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ MacroProcessor::ResolverList resolvers {
+ { "host", host },
+ };
+
+ return MacroProcessor::ResolveMacros(host->GetNotes(), resolvers);
+}
+
+Value HostsTable::NotesUrlAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetNotesUrl();
+}
+
+Value HostsTable::NotesUrlExpandedAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ MacroProcessor::ResolverList resolvers {
+ { "host", host },
+ };
+
+ return MacroProcessor::ResolveMacros(host->GetNotesUrl(), resolvers);
+}
+
+Value HostsTable::ActionUrlAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetActionUrl();
+}
+
+Value HostsTable::ActionUrlExpandedAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ MacroProcessor::ResolverList resolvers {
+ { "host", host },
+ };
+
+ return MacroProcessor::ResolveMacros(host->GetActionUrl(), resolvers);
+}
+
+Value HostsTable::PluginOutputAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ String output;
+ CheckResult::Ptr cr = host->GetLastCheckResult();
+
+ if (cr)
+ output = CompatUtility::GetCheckResultOutput(cr);
+
+ return output;
+}
+
+Value HostsTable::PerfDataAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ String perfdata;
+ CheckResult::Ptr cr = host->GetLastCheckResult();
+
+ if (!cr)
+ return Empty;
+
+ return PluginUtility::FormatPerfdata(cr->GetPerformanceData());
+}
+
+Value HostsTable::IconImageAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetIconImage();
+}
+
+Value HostsTable::IconImageExpandedAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ MacroProcessor::ResolverList resolvers {
+ { "host", host },
+ };
+
+ return MacroProcessor::ResolveMacros(host->GetIconImage(), resolvers);
+}
+
+Value HostsTable::IconImageAltAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetIconImageAlt();
+}
+
+Value HostsTable::LongPluginOutputAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ String long_output;
+ CheckResult::Ptr cr = host->GetLastCheckResult();
+
+ if (cr)
+ long_output = CompatUtility::GetCheckResultLongOutput(cr);
+
+ return long_output;
+}
+
+Value HostsTable::MaxCheckAttemptsAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetMaxCheckAttempts();
+}
+
+Value HostsTable::FlapDetectionEnabledAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return Convert::ToLong(host->GetEnableFlapping());
+}
+
+Value HostsTable::AcceptPassiveChecksAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return Convert::ToLong(host->GetEnablePassiveChecks());
+}
+
+Value HostsTable::EventHandlerEnabledAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return Convert::ToLong(host->GetEnableEventHandler());
+}
+
+Value HostsTable::AcknowledgementTypeAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ObjectLock olock(host);
+ return host->GetAcknowledgement();
+}
+
+Value HostsTable::CheckTypeAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return (host->GetEnableActiveChecks() ? 0 : 1); /* 0 .. active, 1 .. passive */
+}
+
+Value HostsTable::LastStateAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetLastState();
+}
+
+Value HostsTable::LastHardStateAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetLastHardState();
+}
+
+Value HostsTable::CurrentAttemptAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetCheckAttempt();
+}
+
+Value HostsTable::LastNotificationAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return CompatUtility::GetCheckableNotificationLastNotification(host);
+}
+
+Value HostsTable::NextNotificationAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return CompatUtility::GetCheckableNotificationNextNotification(host);
+}
+
+Value HostsTable::NextCheckAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return static_cast<int>(host->GetNextCheck());
+}
+
+Value HostsTable::LastHardStateChangeAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return static_cast<int>(host->GetLastHardStateChange());
+}
+
+Value HostsTable::HasBeenCheckedAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return Convert::ToLong(host->HasBeenChecked());
+}
+
+Value HostsTable::CurrentNotificationNumberAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return CompatUtility::GetCheckableNotificationNotificationNumber(host);
+}
+
+Value HostsTable::TotalServicesAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetTotalServices();
+}
+
+Value HostsTable::ChecksEnabledAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return Convert::ToLong(host->GetEnableActiveChecks());
+}
+
+Value HostsTable::NotificationsEnabledAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return Convert::ToLong(host->GetEnableNotifications());
+}
+
+Value HostsTable::ProcessPerformanceDataAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return Convert::ToLong(host->GetEnablePerfdata());
+}
+
+Value HostsTable::AcknowledgedAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ObjectLock olock(host);
+ return host->IsAcknowledged();
+}
+
+Value HostsTable::StateAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->IsReachable() ? host->GetState() : 2;
+}
+
+Value HostsTable::StateTypeAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetStateType();
+}
+
+Value HostsTable::NoMoreNotificationsAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return (CompatUtility::GetCheckableNotificationNotificationInterval(host) == 0 && !host->GetVolatile()) ? 1 : 0;
+}
+
+Value HostsTable::LastCheckAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return static_cast<int>(host->GetLastCheck());
+}
+
+Value HostsTable::LastStateChangeAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return static_cast<int>(host->GetLastStateChange());
+}
+
+Value HostsTable::LastTimeUpAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return static_cast<int>(host->GetLastStateUp());
+}
+
+Value HostsTable::LastTimeDownAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return static_cast<int>(host->GetLastStateDown());
+}
+
+Value HostsTable::LastTimeUnreachableAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return static_cast<int>(host->GetLastStateUnreachable());
+}
+
+Value HostsTable::IsFlappingAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->IsFlapping();
+}
+
+Value HostsTable::ScheduledDowntimeDepthAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetDowntimeDepth();
+}
+
+Value HostsTable::ActiveChecksEnabledAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return Convert::ToLong(host->GetEnableActiveChecks());
+}
+
+Value HostsTable::CheckIntervalAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetCheckInterval() / LIVESTATUS_INTERVAL_LENGTH;
+}
+
+Value HostsTable::RetryIntervalAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetRetryInterval() / LIVESTATUS_INTERVAL_LENGTH;
+}
+
+Value HostsTable::NotificationIntervalAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return CompatUtility::GetCheckableNotificationNotificationInterval(host);
+}
+
+Value HostsTable::LowFlapThresholdAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetFlappingThresholdLow();
+}
+
+Value HostsTable::HighFlapThresholdAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetFlappingThresholdHigh();
+}
+
+Value HostsTable::LatencyAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ CheckResult::Ptr cr = host->GetLastCheckResult();
+
+ if (!cr)
+ return Empty;
+
+ return cr->CalculateLatency();
+}
+
+Value HostsTable::ExecutionTimeAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ CheckResult::Ptr cr = host->GetLastCheckResult();
+
+ if (!cr)
+ return Empty;
+
+ return cr->CalculateExecutionTime();
+}
+
+Value HostsTable::PercentStateChangeAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetFlappingCurrent();
+}
+
+Value HostsTable::InNotificationPeriodAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ for (const Notification::Ptr& notification : host->GetNotifications()) {
+ TimePeriod::Ptr timeperiod = notification->GetPeriod();
+
+ if (!timeperiod || timeperiod->IsInside(Utility::GetTime()))
+ return 1;
+ }
+
+ return 0;
+}
+
+Value HostsTable::InCheckPeriodAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ TimePeriod::Ptr timeperiod = host->GetCheckPeriod();
+
+ /* none set means always checked */
+ if (!timeperiod)
+ return 1;
+
+ return Convert::ToLong(timeperiod->IsInside(Utility::GetTime()));
+}
+
+Value HostsTable::ContactsAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ArrayData result;
+
+ for (const User::Ptr& user : CompatUtility::GetCheckableNotificationUsers(host)) {
+ result.push_back(user->GetName());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::DowntimesAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Downtime::Ptr& downtime : host->GetDowntimes()) {
+ if (downtime->IsExpired())
+ continue;
+
+ result.push_back(downtime->GetLegacyId());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::DowntimesWithInfoAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Downtime::Ptr& downtime : host->GetDowntimes()) {
+ if (downtime->IsExpired())
+ continue;
+
+ result.push_back(new Array({
+ downtime->GetLegacyId(),
+ downtime->GetAuthor(),
+ downtime->GetComment()
+ }));
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::CommentsAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Comment::Ptr& comment : host->GetComments()) {
+ if (comment->IsExpired())
+ continue;
+
+ result.push_back(comment->GetLegacyId());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::CommentsWithInfoAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Comment::Ptr& comment : host->GetComments()) {
+ if (comment->IsExpired())
+ continue;
+
+ result.push_back(new Array({
+ comment->GetLegacyId(),
+ comment->GetAuthor(),
+ comment->GetText()
+ }));
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::CommentsWithExtraInfoAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Comment::Ptr& comment : host->GetComments()) {
+ if (comment->IsExpired())
+ continue;
+
+ result.push_back(new Array({
+ comment->GetLegacyId(),
+ comment->GetAuthor(),
+ comment->GetText(),
+ comment->GetEntryType(),
+ static_cast<int>(comment->GetEntryTime())
+ }));
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::CustomVariableNamesAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ Dictionary::Ptr vars = host->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ result.push_back(kv.first);
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::CustomVariableValuesAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ Dictionary::Ptr vars = host->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>())
+ result.push_back(JsonEncode(kv.second));
+ else
+ result.push_back(kv.second);
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::CustomVariablesAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ Dictionary::Ptr vars = host->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ Value val;
+
+ if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>())
+ val = JsonEncode(kv.second);
+ else
+ val = kv.second;
+
+ result.push_back(new Array({
+ kv.first,
+ val
+ }));
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::CVIsJsonAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ Dictionary::Ptr vars = host->GetVars();
+
+ if (!vars)
+ return Empty;
+
+ bool cv_is_json = false;
+
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>())
+ cv_is_json = true;
+ }
+
+ return cv_is_json;
+}
+
+Value HostsTable::ParentsAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Checkable::Ptr& parent : host->GetParents()) {
+ Host::Ptr parent_host = dynamic_pointer_cast<Host>(parent);
+
+ if (!parent_host)
+ continue;
+
+ result.push_back(parent_host->GetName());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::ChildsAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Checkable::Ptr& child : host->GetChildren()) {
+ Host::Ptr child_host = dynamic_pointer_cast<Host>(child);
+
+ if (!child_host)
+ continue;
+
+ result.push_back(child_host->GetName());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::NumServicesAccessor(const Value& row)
+{
+ /* duplicate of TotalServices */
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->GetTotalServices();
+}
+
+Value HostsTable::WorstServiceStateAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ Value worst_service = ServiceOK;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetState() > worst_service)
+ worst_service = service->GetState();
+ }
+
+ return worst_service;
+}
+
+Value HostsTable::NumServicesOkAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetState() == ServiceOK)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value HostsTable::NumServicesWarnAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetState() == ServiceWarning)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value HostsTable::NumServicesCritAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetState() == ServiceCritical)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value HostsTable::NumServicesUnknownAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetState() == ServiceUnknown)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value HostsTable::NumServicesPendingAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (!service->GetLastCheckResult())
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value HostsTable::WorstServiceHardStateAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ Value worst_service = ServiceOK;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetStateType() == StateTypeHard) {
+ if (service->GetState() > worst_service)
+ worst_service = service->GetState();
+ }
+ }
+
+ return worst_service;
+}
+
+Value HostsTable::NumServicesHardOkAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceOK)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value HostsTable::NumServicesHardWarnAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceWarning)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value HostsTable::NumServicesHardCritAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceCritical)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value HostsTable::NumServicesHardUnknownAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceUnknown)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value HostsTable::HardStateAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ if (host->GetState() == HostUp)
+ return HostUp;
+ else if (host->GetStateType() == StateTypeHard)
+ return host->GetState();
+
+ return host->GetLastHardState();
+}
+
+Value HostsTable::StalenessAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ if (host->HasBeenChecked() && host->GetLastCheck() > 0)
+ return (Utility::GetTime() - host->GetLastCheck()) / (host->GetCheckInterval() * 3600);
+
+ return 0.0;
+}
+
+Value HostsTable::GroupsAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ Array::Ptr groups = host->GetGroups();
+
+ if (!groups)
+ return Empty;
+
+ return groups;
+}
+
+Value HostsTable::ContactGroupsAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ ArrayData result;
+
+ for (const UserGroup::Ptr& usergroup : CompatUtility::GetCheckableNotificationUserGroups(host)) {
+ result.push_back(usergroup->GetName());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::ServicesAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ std::vector<Service::Ptr> rservices = host->GetServices();
+
+ ArrayData result;
+ result.reserve(rservices.size());
+
+ for (const Service::Ptr& service : rservices) {
+ result.push_back(service->GetShortName());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::ServicesWithStateAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ std::vector<Service::Ptr> rservices = host->GetServices();
+
+ ArrayData result;
+ result.reserve(rservices.size());
+
+ for (const Service::Ptr& service : rservices) {
+ result.push_back(new Array({
+ service->GetShortName(),
+ service->GetState(),
+ service->HasBeenChecked() ? 1 : 0
+ }));
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::ServicesWithInfoAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ std::vector<Service::Ptr> rservices = host->GetServices();
+
+ ArrayData result;
+ result.reserve(rservices.size());
+
+ for (const Service::Ptr& service : rservices) {
+ String output;
+ CheckResult::Ptr cr = service->GetLastCheckResult();
+
+ if (cr)
+ output = CompatUtility::GetCheckResultOutput(cr);
+
+ result.push_back(new Array({
+ service->GetShortName(),
+ service->GetState(),
+ service->HasBeenChecked() ? 1 : 0,
+ output
+ }));
+ }
+
+ return new Array(std::move(result));
+}
+
+Value HostsTable::CheckSourceAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ CheckResult::Ptr cr = host->GetLastCheckResult();
+
+ if (cr)
+ return cr->GetCheckSource();
+
+ return Empty;
+}
+
+Value HostsTable::IsReachableAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return host->IsReachable();
+}
+
+Value HostsTable::OriginalAttributesAccessor(const Value& row)
+{
+ Host::Ptr host = static_cast<Host::Ptr>(row);
+
+ if (!host)
+ return Empty;
+
+ return JsonEncode(host->GetOriginalAttributes());
+}
diff --git a/lib/livestatus/hoststable.hpp b/lib/livestatus/hoststable.hpp
new file mode 100644
index 0000000..9386183
--- /dev/null
+++ b/lib/livestatus/hoststable.hpp
@@ -0,0 +1,133 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef HOSTSTABLE_H
+#define HOSTSTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class HostsTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(HostsTable);
+
+ HostsTable(LivestatusGroupByType type = LivestatusGroupByNone);
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Object::Ptr HostGroupAccessor(const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject);
+
+ static Value NameAccessor(const Value& row);
+ static Value DisplayNameAccessor(const Value& row);
+ static Value AddressAccessor(const Value& row);
+ static Value Address6Accessor(const Value& row);
+ static Value CheckCommandAccessor(const Value& row);
+ static Value CheckCommandExpandedAccessor(const Value& row);
+ static Value EventHandlerAccessor(const Value& row);
+ static Value CheckPeriodAccessor(const Value& row);
+ static Value NotesAccessor(const Value& row);
+ static Value NotesExpandedAccessor(const Value& row);
+ static Value NotesUrlAccessor(const Value& row);
+ static Value NotesUrlExpandedAccessor(const Value& row);
+ static Value ActionUrlAccessor(const Value& row);
+ static Value ActionUrlExpandedAccessor(const Value& row);
+ static Value PluginOutputAccessor(const Value& row);
+ static Value PerfDataAccessor(const Value& row);
+ static Value IconImageAccessor(const Value& row);
+ static Value IconImageExpandedAccessor(const Value& row);
+ static Value IconImageAltAccessor(const Value& row);
+ static Value LongPluginOutputAccessor(const Value& row);
+ static Value MaxCheckAttemptsAccessor(const Value& row);
+ static Value FlapDetectionEnabledAccessor(const Value& row);
+ static Value ProcessPerformanceDataAccessor(const Value& row);
+ static Value AcceptPassiveChecksAccessor(const Value& row);
+ static Value EventHandlerEnabledAccessor(const Value& row);
+ static Value AcknowledgementTypeAccessor(const Value& row);
+ static Value CheckTypeAccessor(const Value& row);
+ static Value LastStateAccessor(const Value& row);
+ static Value LastHardStateAccessor(const Value& row);
+ static Value CurrentAttemptAccessor(const Value& row);
+ static Value LastNotificationAccessor(const Value& row);
+ static Value NextNotificationAccessor(const Value& row);
+ static Value NextCheckAccessor(const Value& row);
+ static Value LastHardStateChangeAccessor(const Value& row);
+ static Value HasBeenCheckedAccessor(const Value& row);
+ static Value CurrentNotificationNumberAccessor(const Value& row);
+ static Value TotalServicesAccessor(const Value& row);
+ static Value ChecksEnabledAccessor(const Value& row);
+ static Value NotificationsEnabledAccessor(const Value& row);
+ static Value AcknowledgedAccessor(const Value& row);
+ static Value StateAccessor(const Value& row);
+ static Value StateTypeAccessor(const Value& row);
+ static Value NoMoreNotificationsAccessor(const Value& row);
+ static Value LastCheckAccessor(const Value& row);
+ static Value LastStateChangeAccessor(const Value& row);
+ static Value LastTimeUpAccessor(const Value& row);
+ static Value LastTimeDownAccessor(const Value& row);
+ static Value LastTimeUnreachableAccessor(const Value& row);
+ static Value IsFlappingAccessor(const Value& row);
+ static Value ScheduledDowntimeDepthAccessor(const Value& row);
+ static Value ActiveChecksEnabledAccessor(const Value& row);
+ static Value CheckIntervalAccessor(const Value& row);
+ static Value RetryIntervalAccessor(const Value& row);
+ static Value NotificationIntervalAccessor(const Value& row);
+ static Value LowFlapThresholdAccessor(const Value& row);
+ static Value HighFlapThresholdAccessor(const Value& row);
+ static Value LatencyAccessor(const Value& row);
+ static Value ExecutionTimeAccessor(const Value& row);
+ static Value PercentStateChangeAccessor(const Value& row);
+ static Value InNotificationPeriodAccessor(const Value& row);
+ static Value InCheckPeriodAccessor(const Value& row);
+ static Value ContactsAccessor(const Value& row);
+ static Value DowntimesAccessor(const Value& row);
+ static Value DowntimesWithInfoAccessor(const Value& row);
+ static Value CommentsAccessor(const Value& row);
+ static Value CommentsWithInfoAccessor(const Value& row);
+ static Value CommentsWithExtraInfoAccessor(const Value& row);
+ static Value CustomVariableNamesAccessor(const Value& row);
+ static Value CustomVariableValuesAccessor(const Value& row);
+ static Value CustomVariablesAccessor(const Value& row);
+ static Value ParentsAccessor(const Value& row);
+ static Value ChildsAccessor(const Value& row);
+ static Value NumServicesAccessor(const Value& row);
+ static Value WorstServiceStateAccessor(const Value& row);
+ static Value NumServicesOkAccessor(const Value& row);
+ static Value NumServicesWarnAccessor(const Value& row);
+ static Value NumServicesCritAccessor(const Value& row);
+ static Value NumServicesUnknownAccessor(const Value& row);
+ static Value NumServicesPendingAccessor(const Value& row);
+ static Value WorstServiceHardStateAccessor(const Value& row);
+ static Value NumServicesHardOkAccessor(const Value& row);
+ static Value NumServicesHardWarnAccessor(const Value& row);
+ static Value NumServicesHardCritAccessor(const Value& row);
+ static Value NumServicesHardUnknownAccessor(const Value& row);
+ static Value HardStateAccessor(const Value& row);
+ static Value StalenessAccessor(const Value& row);
+ static Value GroupsAccessor(const Value& row);
+ static Value ContactGroupsAccessor(const Value& row);
+ static Value ServicesAccessor(const Value& row);
+ static Value ServicesWithStateAccessor(const Value& row);
+ static Value ServicesWithInfoAccessor(const Value& row);
+ static Value CheckSourceAccessor(const Value& row);
+ static Value IsReachableAccessor(const Value& row);
+ static Value CVIsJsonAccessor(const Value& row);
+ static Value OriginalAttributesAccessor(const Value& row);
+};
+
+}
+
+#endif /* HOSTSTABLE_H */
diff --git a/lib/livestatus/i2-livestatus.hpp b/lib/livestatus/i2-livestatus.hpp
new file mode 100644
index 0000000..3375d97
--- /dev/null
+++ b/lib/livestatus/i2-livestatus.hpp
@@ -0,0 +1,14 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef I2LIVESTATUS_H
+#define I2LIVESTATUS_H
+
+/**
+ * @defgroup icinga Livestatus
+ *
+ * The Livestatus library implements the Livestatus protocol for Icinga.
+ */
+
+#include "base/i2-base.hpp"
+
+#endif /* I2LIVESTATUS_H */
diff --git a/lib/livestatus/invavgaggregator.cpp b/lib/livestatus/invavgaggregator.cpp
new file mode 100644
index 0000000..33cf85c
--- /dev/null
+++ b/lib/livestatus/invavgaggregator.cpp
@@ -0,0 +1,38 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/invavgaggregator.hpp"
+
+using namespace icinga;
+
+InvAvgAggregator::InvAvgAggregator(String attr)
+ : m_InvAvgAttr(std::move(attr))
+{ }
+
+InvAvgAggregatorState *InvAvgAggregator::EnsureState(AggregatorState **state)
+{
+ if (!*state)
+ *state = new InvAvgAggregatorState();
+
+ return static_cast<InvAvgAggregatorState *>(*state);
+}
+
+void InvAvgAggregator::Apply(const Table::Ptr& table, const Value& row, AggregatorState **state)
+{
+ Column column = table->GetColumn(m_InvAvgAttr);
+
+ Value value = column.ExtractValue(row);
+
+ InvAvgAggregatorState *pstate = EnsureState(state);
+
+ pstate->InvAvg += (1.0 / value);
+ pstate->InvAvgCount++;
+}
+
+double InvAvgAggregator::GetResultAndFreeState(AggregatorState *state) const
+{
+ InvAvgAggregatorState *pstate = EnsureState(&state);
+ double result = pstate->InvAvg / pstate->InvAvgCount;
+ delete pstate;
+
+ return result;
+}
diff --git a/lib/livestatus/invavgaggregator.hpp b/lib/livestatus/invavgaggregator.hpp
new file mode 100644
index 0000000..9282b37
--- /dev/null
+++ b/lib/livestatus/invavgaggregator.hpp
@@ -0,0 +1,42 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef INVAVGAGGREGATOR_H
+#define INVAVGAGGREGATOR_H
+
+#include "livestatus/table.hpp"
+#include "livestatus/aggregator.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+struct InvAvgAggregatorState final : public AggregatorState
+{
+ double InvAvg{0};
+ double InvAvgCount{0};
+};
+
+/**
+ * @ingroup livestatus
+ */
+class InvAvgAggregator final : public Aggregator
+{
+public:
+ DECLARE_PTR_TYPEDEFS(InvAvgAggregator);
+
+ InvAvgAggregator(String attr);
+
+ void Apply(const Table::Ptr& table, const Value& row, AggregatorState **state) override;
+ double GetResultAndFreeState(AggregatorState *state) const override;
+
+private:
+ String m_InvAvgAttr;
+
+ static InvAvgAggregatorState *EnsureState(AggregatorState **state);
+};
+
+}
+
+#endif /* INVAVGAGGREGATOR_H */
diff --git a/lib/livestatus/invsumaggregator.cpp b/lib/livestatus/invsumaggregator.cpp
new file mode 100644
index 0000000..c955667
--- /dev/null
+++ b/lib/livestatus/invsumaggregator.cpp
@@ -0,0 +1,37 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/invsumaggregator.hpp"
+
+using namespace icinga;
+
+InvSumAggregator::InvSumAggregator(String attr)
+ : m_InvSumAttr(std::move(attr))
+{ }
+
+InvSumAggregatorState *InvSumAggregator::EnsureState(AggregatorState **state)
+{
+ if (!*state)
+ *state = new InvSumAggregatorState();
+
+ return static_cast<InvSumAggregatorState *>(*state);
+}
+
+void InvSumAggregator::Apply(const Table::Ptr& table, const Value& row, AggregatorState **state)
+{
+ Column column = table->GetColumn(m_InvSumAttr);
+
+ Value value = column.ExtractValue(row);
+
+ InvSumAggregatorState *pstate = EnsureState(state);
+
+ pstate->InvSum += (1.0 / value);
+}
+
+double InvSumAggregator::GetResultAndFreeState(AggregatorState *state) const
+{
+ InvSumAggregatorState *pstate = EnsureState(&state);
+ double result = pstate->InvSum;
+ delete pstate;
+
+ return result;
+}
diff --git a/lib/livestatus/invsumaggregator.hpp b/lib/livestatus/invsumaggregator.hpp
new file mode 100644
index 0000000..f7de7be
--- /dev/null
+++ b/lib/livestatus/invsumaggregator.hpp
@@ -0,0 +1,41 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef INVSUMAGGREGATOR_H
+#define INVSUMAGGREGATOR_H
+
+#include "livestatus/table.hpp"
+#include "livestatus/aggregator.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+struct InvSumAggregatorState final : public AggregatorState
+{
+ double InvSum{0};
+};
+
+/**
+ * @ingroup livestatus
+ */
+class InvSumAggregator final : public Aggregator
+{
+public:
+ DECLARE_PTR_TYPEDEFS(InvSumAggregator);
+
+ InvSumAggregator(String attr);
+
+ void Apply(const Table::Ptr& table, const Value& row, AggregatorState **state) override;
+ double GetResultAndFreeState(AggregatorState *state) const override;
+
+private:
+ String m_InvSumAttr;
+
+ static InvSumAggregatorState *EnsureState(AggregatorState **state);
+};
+
+}
+
+#endif /* INVSUMAGGREGATOR_H */
diff --git a/lib/livestatus/livestatuslistener.cpp b/lib/livestatus/livestatuslistener.cpp
new file mode 100644
index 0000000..e44650b
--- /dev/null
+++ b/lib/livestatus/livestatuslistener.cpp
@@ -0,0 +1,211 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/livestatuslistener.hpp"
+#include "livestatus/livestatuslistener-ti.cpp"
+#include "base/utility.hpp"
+#include "base/perfdatavalue.hpp"
+#include "base/objectlock.hpp"
+#include "base/configtype.hpp"
+#include "base/logger.hpp"
+#include "base/exception.hpp"
+#include "base/tcpsocket.hpp"
+#include "base/unixsocket.hpp"
+#include "base/networkstream.hpp"
+#include "base/application.hpp"
+#include "base/function.hpp"
+#include "base/statsfunction.hpp"
+#include "base/convert.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(LivestatusListener);
+
+static int l_ClientsConnected = 0;
+static int l_Connections = 0;
+static std::mutex l_ComponentMutex;
+
+REGISTER_STATSFUNCTION(LivestatusListener, &LivestatusListener::StatsFunc);
+
+void LivestatusListener::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata)
+{
+ DictionaryData nodes;
+
+ for (const LivestatusListener::Ptr& livestatuslistener : ConfigType::GetObjectsByType<LivestatusListener>()) {
+ nodes.emplace_back(livestatuslistener->GetName(), new Dictionary({
+ { "connections", l_Connections }
+ }));
+
+ perfdata->Add(new PerfdataValue("livestatuslistener_" + livestatuslistener->GetName() + "_connections", l_Connections));
+ }
+
+ status->Set("livestatuslistener", new Dictionary(std::move(nodes)));
+}
+
+/**
+ * Starts the component.
+ */
+void LivestatusListener::Start(bool runtimeCreated)
+{
+ ObjectImpl<LivestatusListener>::Start(runtimeCreated);
+
+ Log(LogInformation, "LivestatusListener")
+ << "'" << GetName() << "' started.";
+
+ if (GetSocketType() == "tcp") {
+ TcpSocket::Ptr socket = new TcpSocket();
+
+ try {
+ socket->Bind(GetBindHost(), GetBindPort(), AF_UNSPEC);
+ } catch (std::exception&) {
+ Log(LogCritical, "LivestatusListener")
+ << "Cannot bind TCP socket on host '" << GetBindHost() << "' port '" << GetBindPort() << "'.";
+ return;
+ }
+
+ m_Listener = socket;
+
+ m_Thread = std::thread([this]() { ServerThreadProc(); });
+
+ Log(LogInformation, "LivestatusListener")
+ << "Created TCP socket listening on host '" << GetBindHost() << "' port '" << GetBindPort() << "'.";
+ }
+ else if (GetSocketType() == "unix") {
+#ifndef _WIN32
+ UnixSocket::Ptr socket = new UnixSocket();
+
+ try {
+ socket->Bind(GetSocketPath());
+ } catch (std::exception&) {
+ Log(LogCritical, "LivestatusListener")
+ << "Cannot bind UNIX socket to '" << GetSocketPath() << "'.";
+ return;
+ }
+
+ /* group must be able to write */
+ mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
+
+ if (chmod(GetSocketPath().CStr(), mode) < 0) {
+ Log(LogCritical, "LivestatusListener")
+ << "chmod() on unix socket '" << GetSocketPath() << "' failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
+ return;
+ }
+
+ m_Listener = socket;
+
+ m_Thread = std::thread([this]() { ServerThreadProc(); });
+
+ Log(LogInformation, "LivestatusListener")
+ << "Created UNIX socket in '" << GetSocketPath() << "'.";
+#else
+ /* no UNIX sockets on windows */
+ Log(LogCritical, "LivestatusListener", "Unix sockets are not supported on Windows.");
+ return;
+#endif
+ }
+}
+
+void LivestatusListener::Stop(bool runtimeRemoved)
+{
+ ObjectImpl<LivestatusListener>::Stop(runtimeRemoved);
+
+ Log(LogInformation, "LivestatusListener")
+ << "'" << GetName() << "' stopped.";
+
+ m_Listener->Close();
+
+ if (m_Thread.joinable())
+ m_Thread.join();
+}
+
+int LivestatusListener::GetClientsConnected()
+{
+ std::unique_lock<std::mutex> lock(l_ComponentMutex);
+
+ return l_ClientsConnected;
+}
+
+int LivestatusListener::GetConnections()
+{
+ std::unique_lock<std::mutex> lock(l_ComponentMutex);
+
+ return l_Connections;
+}
+
+void LivestatusListener::ServerThreadProc()
+{
+ m_Listener->Listen();
+
+ try {
+ for (;;) {
+ timeval tv = { 0, 500000 };
+
+ if (m_Listener->Poll(true, false, &tv)) {
+ Socket::Ptr client = m_Listener->Accept();
+ Log(LogNotice, "LivestatusListener", "Client connected");
+ Utility::QueueAsyncCallback([this, client]() { ClientHandler(client); }, LowLatencyScheduler);
+ }
+
+ if (!IsActive())
+ break;
+ }
+ } catch (std::exception&) {
+ Log(LogCritical, "LivestatusListener", "Cannot accept new connection.");
+ }
+
+ m_Listener->Close();
+}
+
+void LivestatusListener::ClientHandler(const Socket::Ptr& client)
+{
+ {
+ std::unique_lock<std::mutex> lock(l_ComponentMutex);
+ l_ClientsConnected++;
+ l_Connections++;
+ }
+
+ Stream::Ptr stream = new NetworkStream(client);
+
+ StreamReadContext context;
+
+ for (;;) {
+ String line;
+
+ std::vector<String> lines;
+
+ for (;;) {
+ StreamReadStatus srs = stream->ReadLine(&line, context);
+
+ if (srs == StatusEof)
+ break;
+
+ if (srs != StatusNewItem)
+ continue;
+
+ if (line.GetLength() > 0)
+ lines.push_back(line);
+ else
+ break;
+ }
+
+ if (lines.empty())
+ break;
+
+ LivestatusQuery::Ptr query = new LivestatusQuery(lines, GetCompatLogPath());
+ if (!query->Execute(stream))
+ break;
+ }
+
+ {
+ std::unique_lock<std::mutex> lock(l_ComponentMutex);
+ l_ClientsConnected--;
+ }
+}
+
+
+void LivestatusListener::ValidateSocketType(const Lazy<String>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<LivestatusListener>::ValidateSocketType(lvalue, utils);
+
+ if (lvalue() != "unix" && lvalue() != "tcp")
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "socket_type" }, "Socket type '" + lvalue() + "' is invalid."));
+}
diff --git a/lib/livestatus/livestatuslistener.hpp b/lib/livestatus/livestatuslistener.hpp
new file mode 100644
index 0000000..dc739f6
--- /dev/null
+++ b/lib/livestatus/livestatuslistener.hpp
@@ -0,0 +1,47 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef LIVESTATUSLISTENER_H
+#define LIVESTATUSLISTENER_H
+
+#include "livestatus/i2-livestatus.hpp"
+#include "livestatus/livestatuslistener-ti.hpp"
+#include "livestatus/livestatusquery.hpp"
+#include "base/socket.hpp"
+#include <thread>
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class LivestatusListener final : public ObjectImpl<LivestatusListener>
+{
+public:
+ DECLARE_OBJECT(LivestatusListener);
+ DECLARE_OBJECTNAME(LivestatusListener);
+
+ static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata);
+
+ static int GetClientsConnected();
+ static int GetConnections();
+
+ void ValidateSocketType(const Lazy<String>& lvalue, const ValidationUtils& utils) override;
+
+protected:
+ void Start(bool runtimeCreated) override;
+ void Stop(bool runtimeRemoved) override;
+
+private:
+ void ServerThreadProc();
+ void ClientHandler(const Socket::Ptr& client);
+
+ Socket::Ptr m_Listener;
+ std::thread m_Thread;
+};
+
+}
+
+#endif /* LIVESTATUSLISTENER_H */
diff --git a/lib/livestatus/livestatuslistener.ti b/lib/livestatus/livestatuslistener.ti
new file mode 100644
index 0000000..31482cf
--- /dev/null
+++ b/lib/livestatus/livestatuslistener.ti
@@ -0,0 +1,31 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/configobject.hpp"
+#include "base/application.hpp"
+
+library livestatus;
+
+namespace icinga
+{
+
+class LivestatusListener : ConfigObject {
+ activation_priority 100;
+
+ [config] String socket_type {
+ default {{{ return "unix"; }}}
+ };
+ [config] String socket_path {
+ default {{{ return Configuration::InitRunDir + "/cmd/livestatus"; }}}
+ };
+ [config] String bind_host {
+ default {{{ return "127.0.0.1"; }}}
+ };
+ [config] String bind_port {
+ default {{{ return "6558"; }}}
+ };
+ [config] String compat_log_path {
+ default {{{ return Configuration::LogDir + "/compat"; }}}
+ };
+};
+
+}
diff --git a/lib/livestatus/livestatuslogutility.cpp b/lib/livestatus/livestatuslogutility.cpp
new file mode 100644
index 0000000..565c2ca
--- /dev/null
+++ b/lib/livestatus/livestatuslogutility.cpp
@@ -0,0 +1,321 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/livestatuslogutility.hpp"
+#include "icinga/service.hpp"
+#include "icinga/host.hpp"
+#include "icinga/user.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "base/utility.hpp"
+#include "base/convert.hpp"
+#include "base/logger.hpp"
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <fstream>
+
+using namespace icinga;
+
+void LivestatusLogUtility::CreateLogIndex(const String& path, std::map<time_t, String>& index)
+{
+ Utility::Glob(path + "/icinga.log", [&index](const String& newPath) { CreateLogIndexFileHandler(newPath, index); }, GlobFile);
+ Utility::Glob(path + "/archives/*.log", [&index](const String& newPath) { CreateLogIndexFileHandler(newPath, index); }, GlobFile);
+}
+
+void LivestatusLogUtility::CreateLogIndexFileHandler(const String& path, std::map<time_t, String>& index)
+{
+ std::ifstream stream;
+ stream.open(path.CStr(), std::ifstream::in);
+
+ if (!stream)
+ BOOST_THROW_EXCEPTION(std::runtime_error("Could not open log file: " + path));
+
+ /* read the first bytes to get the timestamp: [123456789] */
+ char buffer[12];
+
+ stream.read(buffer, 12);
+
+ if (buffer[0] != '[' || buffer[11] != ']') {
+ /* this can happen for directories too, silently ignore them */
+ return;
+ }
+
+ /* extract timestamp */
+ buffer[11] = 0;
+ time_t ts_start = atoi(buffer+1);
+
+ stream.close();
+
+ Log(LogDebug, "LivestatusLogUtility")
+ << "Indexing log file: '" << path << "' with timestamp start: '" << ts_start << "'.";
+
+ index[ts_start] = path;
+}
+
+void LivestatusLogUtility::CreateLogCache(std::map<time_t, String> index, HistoryTable *table,
+ time_t from, time_t until, const AddRowFunction& addRowFn)
+{
+ ASSERT(table);
+
+ /* m_LogFileIndex map tells which log files are involved ordered by their start timestamp */
+ unsigned long line_count = 0;
+ for (const auto& kv : index) {
+ unsigned int ts = kv.first;
+
+ /* skip log files not in range (performance optimization) */
+ if (ts < from || ts > until)
+ continue;
+
+ String log_file = index[ts];
+ int lineno = 0;
+
+ std::ifstream fp;
+ fp.exceptions(std::ifstream::badbit);
+ fp.open(log_file.CStr(), std::ifstream::in);
+
+ while (fp.good()) {
+ std::string line;
+ std::getline(fp, line);
+
+ if (line.empty())
+ continue; /* Ignore empty lines */
+
+ Dictionary::Ptr log_entry_attrs = LivestatusLogUtility::GetAttributes(line);
+
+ /* no attributes available - invalid log line */
+ if (!log_entry_attrs) {
+ Log(LogDebug, "LivestatusLogUtility")
+ << "Skipping invalid log line: '" << line << "'.";
+ continue;
+ }
+
+ table->UpdateLogEntries(log_entry_attrs, line_count, lineno, addRowFn);
+
+ line_count++;
+ lineno++;
+ }
+
+ fp.close();
+ }
+}
+
+Dictionary::Ptr LivestatusLogUtility::GetAttributes(const String& text)
+{
+ Dictionary::Ptr bag = new Dictionary();
+
+ /*
+ * [1379025342] SERVICE NOTIFICATION: contactname;hostname;servicedesc;WARNING;true;foo output
+ */
+ unsigned long time = atoi(text.SubStr(1, 11).CStr());
+
+ Log(LogDebug, "LivestatusLogUtility")
+ << "Processing log line: '" << text << "'.";
+ bag->Set("time", time);
+
+ size_t colon = text.FindFirstOf(':');
+ size_t colon_offset = colon - 13;
+
+ String type = String(text.SubStr(13, colon_offset)).Trim();
+ String options = String(text.SubStr(colon + 1)).Trim();
+
+ bag->Set("type", type);
+ bag->Set("options", options);
+
+ std::vector<String> tokens = options.Split(";");
+
+ /* set default values */
+ bag->Set("class", LogEntryClassInfo);
+ bag->Set("log_type", 0);
+ bag->Set("state", 0);
+ bag->Set("attempt", 0);
+ bag->Set("message", text); /* used as 'message' in log table, and 'log_output' in statehist table */
+
+ if (type.Contains("INITIAL HOST STATE") ||
+ type.Contains("CURRENT HOST STATE") ||
+ type.Contains("HOST ALERT")) {
+ if (tokens.size() < 5)
+ return bag;
+
+ bag->Set("host_name", tokens[0]);
+ bag->Set("state", Host::StateFromString(tokens[1]));
+ bag->Set("state_type", tokens[2]);
+ bag->Set("attempt", atoi(tokens[3].CStr()));
+ bag->Set("plugin_output", tokens[4]);
+
+ if (type.Contains("INITIAL HOST STATE")) {
+ bag->Set("class", LogEntryClassState);
+ bag->Set("log_type", LogEntryTypeHostInitialState);
+ }
+ else if (type.Contains("CURRENT HOST STATE")) {
+ bag->Set("class", LogEntryClassState);
+ bag->Set("log_type", LogEntryTypeHostCurrentState);
+ }
+ else {
+ bag->Set("class", LogEntryClassAlert);
+ bag->Set("log_type", LogEntryTypeHostAlert);
+ }
+
+ return bag;
+ } else if (type.Contains("HOST DOWNTIME ALERT") || type.Contains("HOST FLAPPING ALERT")) {
+ if (tokens.size() < 3)
+ return bag;
+
+ bag->Set("host_name", tokens[0]);
+ bag->Set("state_type", tokens[1]);
+ bag->Set("comment", tokens[2]);
+
+ if (type.Contains("HOST FLAPPING ALERT")) {
+ bag->Set("class", LogEntryClassAlert);
+ bag->Set("log_type", LogEntryTypeHostFlapping);
+ } else {
+ bag->Set("class", LogEntryClassAlert);
+ bag->Set("log_type", LogEntryTypeHostDowntimeAlert);
+ }
+
+ return bag;
+ } else if (type.Contains("INITIAL SERVICE STATE") ||
+ type.Contains("CURRENT SERVICE STATE") ||
+ type.Contains("SERVICE ALERT")) {
+ if (tokens.size() < 6)
+ return bag;
+
+ bag->Set("host_name", tokens[0]);
+ bag->Set("service_description", tokens[1]);
+ bag->Set("state", Service::StateFromString(tokens[2]));
+ bag->Set("state_type", tokens[3]);
+ bag->Set("attempt", atoi(tokens[4].CStr()));
+ bag->Set("plugin_output", tokens[5]);
+
+ if (type.Contains("INITIAL SERVICE STATE")) {
+ bag->Set("class", LogEntryClassState);
+ bag->Set("log_type", LogEntryTypeServiceInitialState);
+ }
+ else if (type.Contains("CURRENT SERVICE STATE")) {
+ bag->Set("class", LogEntryClassState);
+ bag->Set("log_type", LogEntryTypeServiceCurrentState);
+ }
+ else {
+ bag->Set("class", LogEntryClassAlert);
+ bag->Set("log_type", LogEntryTypeServiceAlert);
+ }
+
+ return bag;
+ } else if (type.Contains("SERVICE DOWNTIME ALERT") ||
+ type.Contains("SERVICE FLAPPING ALERT")) {
+ if (tokens.size() < 4)
+ return bag;
+
+ bag->Set("host_name", tokens[0]);
+ bag->Set("service_description", tokens[1]);
+ bag->Set("state_type", tokens[2]);
+ bag->Set("comment", tokens[3]);
+
+ if (type.Contains("SERVICE FLAPPING ALERT")) {
+ bag->Set("class", LogEntryClassAlert);
+ bag->Set("log_type", LogEntryTypeServiceFlapping);
+ } else {
+ bag->Set("class", LogEntryClassAlert);
+ bag->Set("log_type", LogEntryTypeServiceDowntimeAlert);
+ }
+
+ return bag;
+ } else if (type.Contains("TIMEPERIOD TRANSITION")) {
+ if (tokens.size() < 4)
+ return bag;
+
+ bag->Set("class", LogEntryClassState);
+ bag->Set("log_type", LogEntryTypeTimeperiodTransition);
+
+ bag->Set("host_name", tokens[0]);
+ bag->Set("service_description", tokens[1]);
+ bag->Set("state_type", tokens[2]);
+ bag->Set("comment", tokens[3]);
+ } else if (type.Contains("HOST NOTIFICATION")) {
+ if (tokens.size() < 6)
+ return bag;
+
+ bag->Set("contact_name", tokens[0]);
+ bag->Set("host_name", tokens[1]);
+ bag->Set("state_type", tokens[2].CStr());
+ bag->Set("state", Service::StateFromString(tokens[3]));
+ bag->Set("command_name", tokens[4]);
+ bag->Set("plugin_output", tokens[5]);
+
+ bag->Set("class", LogEntryClassNotification);
+ bag->Set("log_type", LogEntryTypeHostNotification);
+
+ return bag;
+ } else if (type.Contains("SERVICE NOTIFICATION")) {
+ if (tokens.size() < 7)
+ return bag;
+
+ bag->Set("contact_name", tokens[0]);
+ bag->Set("host_name", tokens[1]);
+ bag->Set("service_description", tokens[2]);
+ bag->Set("state_type", tokens[3].CStr());
+ bag->Set("state", Service::StateFromString(tokens[4]));
+ bag->Set("command_name", tokens[5]);
+ bag->Set("plugin_output", tokens[6]);
+
+ bag->Set("class", LogEntryClassNotification);
+ bag->Set("log_type", LogEntryTypeServiceNotification);
+
+ return bag;
+ } else if (type.Contains("PASSIVE HOST CHECK")) {
+ if (tokens.size() < 3)
+ return bag;
+
+ bag->Set("host_name", tokens[0]);
+ bag->Set("state", Host::StateFromString(tokens[1]));
+ bag->Set("plugin_output", tokens[2]);
+
+ bag->Set("class", LogEntryClassPassive);
+
+ return bag;
+ } else if (type.Contains("PASSIVE SERVICE CHECK")) {
+ if (tokens.size() < 4)
+ return bag;
+
+ bag->Set("host_name", tokens[0]);
+ bag->Set("service_description", tokens[1]);
+ bag->Set("state", Host::StateFromString(tokens[2]));
+ bag->Set("plugin_output", tokens[3]);
+
+ bag->Set("class", LogEntryClassPassive);
+
+ return bag;
+ } else if (type.Contains("EXTERNAL COMMAND")) {
+ bag->Set("class", LogEntryClassCommand);
+ /* string processing not implemented in 1.x */
+
+ return bag;
+ } else if (type.Contains("LOG VERSION")) {
+ bag->Set("class", LogEntryClassProgram);
+ bag->Set("log_type", LogEntryTypeVersion);
+
+ return bag;
+ } else if (type.Contains("logging initial states")) {
+ bag->Set("class", LogEntryClassProgram);
+ bag->Set("log_type", LogEntryTypeInitialStates);
+
+ return bag;
+ } else if (type.Contains("starting... (PID=")) {
+ bag->Set("class", LogEntryClassProgram);
+ bag->Set("log_type", LogEntryTypeProgramStarting);
+
+ return bag;
+ }
+ /* program */
+ else if (type.Contains("restarting...") ||
+ type.Contains("shutting down...") ||
+ type.Contains("Bailing out") ||
+ type.Contains("active mode...") ||
+ type.Contains("standby mode...")) {
+ bag->Set("class", LogEntryClassProgram);
+
+ return bag;
+ }
+
+ return bag;
+}
diff --git a/lib/livestatus/livestatuslogutility.hpp b/lib/livestatus/livestatuslogutility.hpp
new file mode 100644
index 0000000..66d1154
--- /dev/null
+++ b/lib/livestatus/livestatuslogutility.hpp
@@ -0,0 +1,60 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef LIVESTATUSLOGUTILITY_H
+#define LIVESTATUSLOGUTILITY_H
+
+#include "livestatus/historytable.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+enum LogEntryType {
+ LogEntryTypeHostAlert,
+ LogEntryTypeHostDowntimeAlert,
+ LogEntryTypeHostFlapping,
+ LogEntryTypeHostNotification,
+ LogEntryTypeHostInitialState,
+ LogEntryTypeHostCurrentState,
+ LogEntryTypeServiceAlert,
+ LogEntryTypeServiceDowntimeAlert,
+ LogEntryTypeServiceFlapping,
+ LogEntryTypeServiceNotification,
+ LogEntryTypeServiceInitialState,
+ LogEntryTypeServiceCurrentState,
+ LogEntryTypeTimeperiodTransition,
+ LogEntryTypeVersion,
+ LogEntryTypeInitialStates,
+ LogEntryTypeProgramStarting
+};
+
+enum LogEntryClass {
+ LogEntryClassInfo = 0,
+ LogEntryClassAlert = 1,
+ LogEntryClassProgram = 2,
+ LogEntryClassNotification = 3,
+ LogEntryClassPassive = 4,
+ LogEntryClassCommand = 5,
+ LogEntryClassState = 6,
+ LogEntryClassText = 7
+};
+
+/**
+ * @ingroup livestatus
+ */
+class LivestatusLogUtility
+{
+public:
+ static void CreateLogIndex(const String& path, std::map<time_t, String>& index);
+ static void CreateLogIndexFileHandler(const String& path, std::map<time_t, String>& index);
+ static void CreateLogCache(std::map<time_t, String> index, HistoryTable *table, time_t from, time_t until, const AddRowFunction& addRowFn);
+ static Dictionary::Ptr GetAttributes(const String& text);
+
+private:
+ LivestatusLogUtility();
+};
+
+}
+
+#endif /* LIVESTATUSLOGUTILITY_H */
diff --git a/lib/livestatus/livestatusquery.cpp b/lib/livestatus/livestatusquery.cpp
new file mode 100644
index 0000000..0f9b3da
--- /dev/null
+++ b/lib/livestatus/livestatusquery.cpp
@@ -0,0 +1,648 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/livestatusquery.hpp"
+#include "livestatus/countaggregator.hpp"
+#include "livestatus/sumaggregator.hpp"
+#include "livestatus/minaggregator.hpp"
+#include "livestatus/maxaggregator.hpp"
+#include "livestatus/avgaggregator.hpp"
+#include "livestatus/stdaggregator.hpp"
+#include "livestatus/invsumaggregator.hpp"
+#include "livestatus/invavgaggregator.hpp"
+#include "livestatus/attributefilter.hpp"
+#include "livestatus/negatefilter.hpp"
+#include "livestatus/orfilter.hpp"
+#include "livestatus/andfilter.hpp"
+#include "icinga/externalcommandprocessor.hpp"
+#include "base/debug.hpp"
+#include "base/convert.hpp"
+#include "base/objectlock.hpp"
+#include "base/logger.hpp"
+#include "base/exception.hpp"
+#include "base/utility.hpp"
+#include "base/json.hpp"
+#include "base/serializer.hpp"
+#include "base/timer.hpp"
+#include "base/initialize.hpp"
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/join.hpp>
+
+using namespace icinga;
+
+static int l_ExternalCommands = 0;
+static std::mutex l_QueryMutex;
+
+LivestatusQuery::LivestatusQuery(const std::vector<String>& lines, const String& compat_log_path)
+ : 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";
+ m_ErrorCode = LivestatusErrorQuery;
+ m_ErrorMessage = "Empty Query. Aborting.";
+ return;
+ }
+
+ String msg;
+ for (const String& line : lines) {
+ msg += line + "\n";
+ }
+ Log(LogDebug, "LivestatusQuery", msg);
+
+ m_CompatLogPath = compat_log_path;
+
+ /* default separators */
+ m_Separators.emplace_back("\n");
+ m_Separators.emplace_back(";");
+ m_Separators.emplace_back(",");
+ m_Separators.emplace_back("|");
+
+ String line = lines[0];
+
+ size_t sp_index = line.FindFirstOf(" ");
+
+ if (sp_index == String::NPos)
+ BOOST_THROW_EXCEPTION(std::runtime_error("Livestatus header must contain a verb."));
+
+ String verb = line.SubStr(0, sp_index);
+ String target = line.SubStr(sp_index + 1);
+
+ m_Verb = verb;
+
+ if (m_Verb == "COMMAND") {
+ m_KeepAlive = true;
+ m_Command = target;
+ } else if (m_Verb == "GET") {
+ m_Table = target;
+ } else {
+ m_Verb = "ERROR";
+ m_ErrorCode = LivestatusErrorQuery;
+ m_ErrorMessage = "Unknown livestatus verb: " + m_Verb;
+ return;
+ }
+
+ std::deque<Filter::Ptr> filters, stats;
+ std::deque<Aggregator::Ptr> aggregators;
+
+ for (unsigned int i = 1; i < lines.size(); i++) {
+ line = lines[i];
+
+ size_t col_index = line.FindFirstOf(":");
+ String header = line.SubStr(0, col_index);
+ String params;
+
+ //OutputFormat:json or OutputFormat: json
+ if (line.GetLength() > col_index + 1)
+ params = line.SubStr(col_index + 1).Trim();
+
+ if (header == "ResponseHeader")
+ m_ResponseHeader = params;
+ else if (header == "OutputFormat")
+ m_OutputFormat = params;
+ else if (header == "KeepAlive")
+ m_KeepAlive = (params == "on");
+ else if (header == "Columns") {
+ m_ColumnHeaders = false; // Might be explicitly re-enabled later on
+ m_Columns = params.Split(" ");
+ } else if (header == "Separators") {
+ std::vector<String> separators = params.Split(" ");
+
+ /* ugly ascii long to char conversion, but works */
+ if (separators.size() > 0)
+ m_Separators[0] = String(1, static_cast<char>(Convert::ToLong(separators[0])));
+ if (separators.size() > 1)
+ m_Separators[1] = String(1, static_cast<char>(Convert::ToLong(separators[1])));
+ if (separators.size() > 2)
+ m_Separators[2] = String(1, static_cast<char>(Convert::ToLong(separators[2])));
+ if (separators.size() > 3)
+ 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 (!filter) {
+ m_Verb = "ERROR";
+ m_ErrorCode = LivestatusErrorQuery;
+ m_ErrorMessage = "Invalid filter specification: " + line;
+ return;
+ }
+
+ filters.push_back(filter);
+ } else if (header == "Stats") {
+ m_ColumnHeaders = false; // Might be explicitly re-enabled later on
+
+ std::vector<String> tokens = params.Split(" ");
+
+ if (tokens.size() < 2) {
+ m_Verb = "ERROR";
+ m_ErrorCode = LivestatusErrorQuery;
+ m_ErrorMessage = "Missing aggregator column name: " + line;
+ return;
+ }
+
+ String aggregate_arg = tokens[0];
+ String aggregate_attr = tokens[1];
+
+ Aggregator::Ptr aggregator;
+ Filter::Ptr filter;
+
+ if (aggregate_arg == "sum") {
+ aggregator = new SumAggregator(aggregate_attr);
+ } else if (aggregate_arg == "min") {
+ aggregator = new MinAggregator(aggregate_attr);
+ } else if (aggregate_arg == "max") {
+ aggregator = new MaxAggregator(aggregate_attr);
+ } else if (aggregate_arg == "avg") {
+ aggregator = new AvgAggregator(aggregate_attr);
+ } else if (aggregate_arg == "std") {
+ aggregator = new StdAggregator(aggregate_attr);
+ } else if (aggregate_arg == "suminv") {
+ aggregator = new InvSumAggregator(aggregate_attr);
+ } else if (aggregate_arg == "avginv") {
+ aggregator = new InvAvgAggregator(aggregate_attr);
+ } else {
+ filter = ParseFilter(params, m_LogTimeFrom, m_LogTimeUntil);
+
+ if (!filter) {
+ m_Verb = "ERROR";
+ m_ErrorCode = LivestatusErrorQuery;
+ m_ErrorMessage = "Invalid filter specification: " + line;
+ return;
+ }
+
+ aggregator = new CountAggregator();
+ }
+
+ aggregator->SetFilter(filter);
+ aggregators.push_back(aggregator);
+
+ stats.push_back(filter);
+ } else if (header == "Or" || header == "And" || header == "StatsOr" || header == "StatsAnd") {
+ std::deque<Filter::Ptr>& deq = (header == "Or" || header == "And") ? filters : stats;
+
+ unsigned int num = Convert::ToLong(params);
+ CombinerFilter::Ptr filter;
+
+ if (header == "Or" || header == "StatsOr") {
+ filter = new OrFilter();
+ Log(LogDebug, "LivestatusQuery")
+ << "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.";
+ }
+
+ if (num > deq.size()) {
+ m_Verb = "ERROR";
+ m_ErrorCode = 451;
+ m_ErrorMessage = "Or/StatsOr is referencing " + Convert::ToString(num) + " filters; stack only contains " + Convert::ToString(static_cast<long>(deq.size())) + " filters";
+ return;
+ }
+
+ while (num > 0 && num--) {
+ filter->AddSubFilter(deq.back());
+ Log(LogDebug, "LivestatusQuery")
+ << "Add " << num << " filter.";
+ deq.pop_back();
+ if (&deq == &stats)
+ aggregators.pop_back();
+ }
+
+ deq.emplace_back(filter);
+ if (&deq == &stats) {
+ Aggregator::Ptr aggregator = new CountAggregator();
+ aggregator->SetFilter(filter);
+ aggregators.push_back(aggregator);
+ }
+ } else if (header == "Negate" || header == "StatsNegate") {
+ std::deque<Filter::Ptr>& deq = (header == "Negate") ? filters : stats;
+
+ if (deq.empty()) {
+ m_Verb = "ERROR";
+ m_ErrorCode = 451;
+ m_ErrorMessage = "Negate/StatsNegate used, however the filter stack is empty";
+ return;
+ }
+
+ Filter::Ptr filter = deq.back();
+ deq.pop_back();
+
+ if (!filter) {
+ m_Verb = "ERROR";
+ m_ErrorCode = 451;
+ m_ErrorMessage = "Negate/StatsNegate used, however last stats doesn't have a filter";
+ return;
+ }
+
+ deq.push_back(new NegateFilter(filter));
+
+ if (deq == stats) {
+ Aggregator::Ptr aggregator = aggregators.back();
+ aggregator->SetFilter(filter);
+ }
+ }
+ }
+
+ /* Combine all top-level filters into a single filter. */
+ AndFilter::Ptr top_filter = new AndFilter();
+
+ for (const Filter::Ptr& filter : filters) {
+ top_filter->AddSubFilter(filter);
+ }
+
+ m_Filter = top_filter;
+ m_Aggregators.swap(aggregators);
+}
+
+int LivestatusQuery::GetExternalCommands()
+{
+ std::unique_lock<std::mutex> lock(l_QueryMutex);
+
+ return l_ExternalCommands;
+}
+
+Filter::Ptr LivestatusQuery::ParseFilter(const String& params, unsigned long& from, unsigned long& until)
+{
+ /*
+ * time >= 1382696656
+ * type = SERVICE FLAPPING ALERT
+ */
+ std::vector<String> tokens;
+ size_t sp_index;
+ String temp_buffer = params;
+
+ /* extract attr and op */
+ for (int i = 0; i < 2; i++) {
+ sp_index = temp_buffer.FindFirstOf(" ");
+
+ /* check if this is the last argument */
+ if (sp_index == String::NPos) {
+ /* 'attr op' or 'attr op val' is valid */
+ if (i < 1)
+ BOOST_THROW_EXCEPTION(std::runtime_error("Livestatus filter '" + params + "' does not contain all required fields."));
+
+ break;
+ }
+
+ tokens.emplace_back(temp_buffer.SubStr(0, sp_index));
+ temp_buffer = temp_buffer.SubStr(sp_index + 1);
+ }
+
+ /* add the rest as value */
+ tokens.emplace_back(std::move(temp_buffer));
+
+ if (tokens.size() == 2)
+ tokens.emplace_back("");
+
+ if (tokens.size() < 3)
+ return nullptr;
+
+ bool negate = false;
+ String attr = tokens[0];
+ String op = tokens[1];
+ String val = tokens[2];
+
+ if (op == "!=") {
+ op = "=";
+ negate = true;
+ } else if (op == "!~") {
+ op = "~";
+ negate = true;
+ } else if (op == "!=~") {
+ op = "=~";
+ negate = true;
+ } else if (op == "!~~") {
+ op = "~~";
+ negate = true;
+ }
+
+ Filter::Ptr filter = new AttributeFilter(attr, op, val);
+
+ if (negate)
+ filter = new NegateFilter(filter);
+
+ /* pre-filter log time duration */
+ if (attr == "time") {
+ if (op == "<" || op == "<=") {
+ until = Convert::ToLong(val);
+ } else if (op == ">" || op == ">=") {
+ from = Convert::ToLong(val);
+ }
+ }
+
+ Log(LogDebug, "LivestatusQuery")
+ << "Parsed filter with attr: '" << attr << "' op: '" << op << "' val: '" << val << "'.";
+
+ return filter;
+}
+
+void LivestatusQuery::BeginResultSet(std::ostream& fp) const
+{
+ if (m_OutputFormat == "json" || m_OutputFormat == "python")
+ fp << "[";
+}
+
+void LivestatusQuery::EndResultSet(std::ostream& fp) const
+{
+ if (m_OutputFormat == "json" || m_OutputFormat == "python")
+ fp << "]";
+}
+
+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") {
+ if (!first_row)
+ fp << ", ";
+
+ fp << JsonEncode(row);
+ } else if (m_OutputFormat == "python") {
+ 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);
+ for (const Value& value : array) {
+ if (first)
+ first = false;
+ else
+ fp << ((level == 0) ? m_Separators[2] : m_Separators[3]);
+
+ if (value.IsObjectType<Array>())
+ PrintCsvArray(fp, value, level + 1);
+ else if (value.IsBoolean())
+ fp << Convert::ToLong(value);
+ else
+ fp << value;
+ }
+}
+
+void LivestatusQuery::PrintPythonArray(std::ostream& fp, const Array::Ptr& rs) const
+{
+ fp << "[ ";
+
+ bool first = true;
+
+ for (const Value& value : rs) {
+ if (first)
+ first = false;
+ else
+ fp << ", ";
+
+ if (value.IsObjectType<Array>())
+ PrintPythonArray(fp, value);
+ else if (value.IsNumber())
+ fp << value;
+ else
+ fp << QuoteStringPython(value);
+ }
+
+ fp << " ]";
+}
+
+String LivestatusQuery::QuoteStringPython(const String& str) {
+ String result = str;
+ boost::algorithm::replace_all(result, "\"", "\\\"");
+ return "r\"" + result + "\"";
+}
+
+void LivestatusQuery::ExecuteGetHelper(const Stream::Ptr& stream)
+{
+ Log(LogNotice, "LivestatusQuery")
+ << "Table: " << m_Table;
+
+ Table::Ptr table = Table::GetByName(m_Table, m_CompatLogPath, m_LogTimeFrom, m_LogTimeUntil);
+
+ if (!table) {
+ SendResponse(stream, LivestatusErrorNotFound, "Table '" + m_Table + "' does not exist.");
+
+ return;
+ }
+
+ std::vector<LivestatusRowValue> objects = table->FilterRows(m_Filter, m_Limit);
+ std::vector<String> columns;
+
+ if (m_Columns.size() > 0)
+ columns = m_Columns;
+ else
+ columns = table->GetColumnNames();
+
+ std::ostringstream result;
+ bool first_row = true;
+ BeginResultSet(result);
+
+ if (m_Aggregators.empty()) {
+ 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));
+
+ ArrayData header;
+
+ for (const LivestatusRowValue& object : objects) {
+ ArrayData row;
+
+ row.reserve(column_objs.size());
+
+ for (const ColumnPair& cv : column_objs) {
+ if (m_ColumnHeaders)
+ header.push_back(cv.first);
+
+ row.push_back(cv.second.ExtractValue(object.Row, object.GroupByType, object.GroupByObject));
+ }
+
+ if (m_ColumnHeaders) {
+ AppendResultRow(result, new Array(std::move(header)), first_row);
+ m_ColumnHeaders = false;
+ }
+
+ AppendResultRow(result, new Array(std::move(row)), first_row);
+ }
+ } else {
+ std::map<std::vector<Value>, std::vector<AggregatorState *> > allStats;
+
+ /* add aggregated stats */
+ 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;
+ }
+
+ 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) {
+ ArrayData header;
+
+ for (const String& columnName : m_Columns) {
+ header.push_back(columnName);
+ }
+
+ for (size_t i = 1; i <= m_Aggregators.size(); i++) {
+ header.push_back("stats_" + Convert::ToString(i));
+ }
+
+ AppendResultRow(result, new Array(std::move(header)), first_row);
+ }
+
+ for (const auto& kv : allStats) {
+ ArrayData row;
+
+ row.reserve(m_Columns.size() + m_Aggregators.size());
+
+ for (const Value& keyPart : kv.first) {
+ row.push_back(keyPart);
+ }
+
+ auto& stats = kv.second;
+
+ for (size_t i = 0; i < m_Aggregators.size(); i++)
+ row.push_back(m_Aggregators[i]->GetResultAndFreeState(stats[i]));
+
+ AppendResultRow(result, new Array(std::move(row)), first_row);
+ }
+
+ /* add a bogus zero value if aggregated is empty*/
+ if (allStats.empty()) {
+ ArrayData row;
+
+ row.reserve(m_Aggregators.size());
+
+ for (size_t i = 1; i <= m_Aggregators.size(); i++) {
+ row.push_back(0);
+ }
+
+ AppendResultRow(result, new Array(std::move(row)), first_row);
+ }
+ }
+
+ EndResultSet(result);
+
+ SendResponse(stream, LivestatusErrorOK, result.str());
+}
+
+void LivestatusQuery::ExecuteCommandHelper(const Stream::Ptr& stream)
+{
+ {
+ std::unique_lock<std::mutex> lock(l_QueryMutex);
+
+ l_ExternalCommands++;
+ }
+
+ Log(LogNotice, "LivestatusQuery")
+ << "Executing command: " << m_Command;
+ ExternalCommandProcessor::Execute(m_Command);
+ SendResponse(stream, LivestatusErrorOK, "");
+}
+
+void LivestatusQuery::ExecuteErrorHelper(const Stream::Ptr& stream)
+{
+ Log(LogDebug, "LivestatusQuery")
+ << "ERROR: Code: '" << m_ErrorCode << "' Message: '" << m_ErrorMessage << "'.";
+ SendResponse(stream, m_ErrorCode, m_ErrorMessage);
+}
+
+void LivestatusQuery::SendResponse(const Stream::Ptr& stream, int code, const String& data)
+{
+ if (m_ResponseHeader == "fixed16")
+ PrintFixed16(stream, code, data);
+
+ if (m_ResponseHeader == "fixed16" || code == LivestatusErrorOK) {
+ try {
+ stream->Write(data.CStr(), data.GetLength());
+ } catch (const std::exception&) {
+ Log(LogCritical, "LivestatusQuery", "Cannot write query response to socket.");
+ }
+ }
+}
+
+void LivestatusQuery::PrintFixed16(const Stream::Ptr& stream, int code, const String& data)
+{
+ ASSERT(code >= 100 && code <= 999);
+
+ String sCode = Convert::ToString(code);
+ String sLength = Convert::ToString(static_cast<long>(data.GetLength()));
+
+ String header = sCode + String(16 - 3 - sLength.GetLength() - 1, ' ') + sLength + m_Separators[0];
+
+ try {
+ stream->Write(header.CStr(), header.GetLength());
+ } catch (const std::exception&) {
+ Log(LogCritical, "LivestatusQuery", "Cannot write to TCP socket.");
+ }
+}
+
+bool LivestatusQuery::Execute(const Stream::Ptr& stream)
+{
+ try {
+ 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 == "ERROR")
+ ExecuteErrorHelper(stream);
+ else
+ BOOST_THROW_EXCEPTION(std::runtime_error("Invalid livestatus query verb."));
+ } catch (const std::exception& ex) {
+ SendResponse(stream, LivestatusErrorQuery, DiagnosticInformation(ex));
+ }
+
+ if (!m_KeepAlive) {
+ stream->Close();
+ return false;
+ }
+
+ return true;
+}
diff --git a/lib/livestatus/livestatusquery.hpp b/lib/livestatus/livestatusquery.hpp
new file mode 100644
index 0000000..910cc16
--- /dev/null
+++ b/lib/livestatus/livestatusquery.hpp
@@ -0,0 +1,90 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef LIVESTATUSQUERY_H
+#define LIVESTATUSQUERY_H
+
+#include "livestatus/filter.hpp"
+#include "livestatus/aggregator.hpp"
+#include "base/object.hpp"
+#include "base/array.hpp"
+#include "base/stream.hpp"
+#include "base/scriptframe.hpp"
+#include <deque>
+
+using namespace icinga;
+
+namespace icinga
+{
+
+enum LivestatusError
+{
+ LivestatusErrorOK = 200,
+ LivestatusErrorNotFound = 404,
+ LivestatusErrorQuery = 452
+};
+
+/**
+ * @ingroup livestatus
+ */
+class LivestatusQuery final : public Object
+{
+public:
+ DECLARE_PTR_TYPEDEFS(LivestatusQuery);
+
+ LivestatusQuery(const std::vector<String>& lines, const String& compat_log_path);
+
+ bool Execute(const Stream::Ptr& stream);
+
+ static int GetExternalCommands();
+
+private:
+ String m_Verb;
+
+ bool m_KeepAlive;
+
+ /* Parameters for GET queries. */
+ String m_Table;
+ std::vector<String> m_Columns;
+ std::vector<String> m_Separators;
+
+ Filter::Ptr m_Filter;
+ std::deque<Aggregator::Ptr> m_Aggregators;
+
+ String m_OutputFormat;
+ bool m_ColumnHeaders;
+ int m_Limit;
+
+ String m_ResponseHeader;
+
+ /* Parameters for COMMAND/SCRIPT queries. */
+ String m_Command;
+ String m_Session;
+
+ /* Parameters for invalid queries. */
+ int m_ErrorCode;
+ String m_ErrorMessage;
+
+ unsigned long m_LogTimeFrom;
+ unsigned long m_LogTimeUntil;
+ String m_CompatLogPath;
+
+ void BeginResultSet(std::ostream& fp) const;
+ void EndResultSet(std::ostream& fp) const;
+ void AppendResultRow(std::ostream& fp, const Array::Ptr& row, bool& first_row) const;
+ void PrintCsvArray(std::ostream& fp, const Array::Ptr& array, int level) const;
+ void PrintPythonArray(std::ostream& fp, const Array::Ptr& array) const;
+ static String QuoteStringPython(const String& str);
+
+ void ExecuteGetHelper(const Stream::Ptr& stream);
+ void ExecuteCommandHelper(const Stream::Ptr& stream);
+ void ExecuteErrorHelper(const Stream::Ptr& stream);
+
+ void SendResponse(const Stream::Ptr& stream, int code, const String& data);
+ void PrintFixed16(const Stream::Ptr& stream, int code, const String& data);
+
+ static Filter::Ptr ParseFilter(const String& params, unsigned long& from, unsigned long& until);
+};
+
+}
+
+#endif /* LIVESTATUSQUERY_H */
diff --git a/lib/livestatus/logtable.cpp b/lib/livestatus/logtable.cpp
new file mode 100644
index 0000000..c1358dd
--- /dev/null
+++ b/lib/livestatus/logtable.cpp
@@ -0,0 +1,229 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/logtable.hpp"
+#include "livestatus/livestatuslogutility.hpp"
+#include "livestatus/hoststable.hpp"
+#include "livestatus/servicestable.hpp"
+#include "livestatus/contactstable.hpp"
+#include "livestatus/commandstable.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/cib.hpp"
+#include "icinga/service.hpp"
+#include "icinga/host.hpp"
+#include "icinga/user.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "base/convert.hpp"
+#include "base/utility.hpp"
+#include "base/logger.hpp"
+#include "base/application.hpp"
+#include "base/objectlock.hpp"
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <fstream>
+
+using namespace icinga;
+
+LogTable::LogTable(const String& compat_log_path, time_t from, time_t until)
+{
+ /* store attributes for FetchRows */
+ m_TimeFrom = from;
+ m_TimeUntil = until;
+ m_CompatLogPath = compat_log_path;
+
+ AddColumns(this);
+}
+
+void LogTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "time", Column(&LogTable::TimeAccessor, objectAccessor));
+ table->AddColumn(prefix + "lineno", Column(&LogTable::LinenoAccessor, objectAccessor));
+ table->AddColumn(prefix + "class", Column(&LogTable::ClassAccessor, objectAccessor));
+ table->AddColumn(prefix + "message", Column(&LogTable::MessageAccessor, objectAccessor));
+ table->AddColumn(prefix + "type", Column(&LogTable::TypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "options", Column(&LogTable::OptionsAccessor, objectAccessor));
+ table->AddColumn(prefix + "comment", Column(&LogTable::CommentAccessor, objectAccessor));
+ table->AddColumn(prefix + "plugin_output", Column(&LogTable::PluginOutputAccessor, objectAccessor));
+ table->AddColumn(prefix + "state", Column(&LogTable::StateAccessor, objectAccessor));
+ table->AddColumn(prefix + "state_type", Column(&LogTable::StateTypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "attempt", Column(&LogTable::AttemptAccessor, objectAccessor));
+ table->AddColumn(prefix + "service_description", Column(&LogTable::ServiceDescriptionAccessor, objectAccessor));
+ table->AddColumn(prefix + "host_name", Column(&LogTable::HostNameAccessor, objectAccessor));
+ table->AddColumn(prefix + "contact_name", Column(&LogTable::ContactNameAccessor, objectAccessor));
+ table->AddColumn(prefix + "command_name", Column(&LogTable::CommandNameAccessor, objectAccessor));
+
+ HostsTable::AddColumns(table, "current_host_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return HostAccessor(row, objectAccessor);
+ });
+ ServicesTable::AddColumns(table, "current_service_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return ServiceAccessor(row, objectAccessor);
+ });
+ ContactsTable::AddColumns(table, "current_contact_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return ContactAccessor(row, objectAccessor);
+ });
+ CommandsTable::AddColumns(table, "current_command_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return CommandAccessor(row, objectAccessor);
+ });
+}
+
+String LogTable::GetName() const
+{
+ return "log";
+}
+
+String LogTable::GetPrefix() const
+{
+ return "log";
+}
+
+void LogTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ Log(LogDebug, "LogTable")
+ << "Pre-selecting log file from " << m_TimeFrom << " until " << m_TimeUntil;
+
+ /* create log file index */
+ LivestatusLogUtility::CreateLogIndex(m_CompatLogPath, m_LogFileIndex);
+
+ /* generate log cache */
+ LivestatusLogUtility::CreateLogCache(m_LogFileIndex, this, m_TimeFrom, m_TimeUntil, addRowFn);
+}
+
+/* gets called in LivestatusLogUtility::CreateLogCache */
+void LogTable::UpdateLogEntries(const Dictionary::Ptr& log_entry_attrs, int line_count, int lineno, const AddRowFunction& addRowFn)
+{
+ /* additional attributes only for log table */
+ log_entry_attrs->Set("lineno", lineno);
+
+ addRowFn(log_entry_attrs, LivestatusGroupByNone, Empty);
+}
+
+Object::Ptr LogTable::HostAccessor(const Value& row, const Column::ObjectAccessor&)
+{
+ String host_name = static_cast<Dictionary::Ptr>(row)->Get("host_name");
+
+ if (host_name.IsEmpty())
+ return nullptr;
+
+ return Host::GetByName(host_name);
+}
+
+Object::Ptr LogTable::ServiceAccessor(const Value& row, const Column::ObjectAccessor&)
+{
+ String host_name = static_cast<Dictionary::Ptr>(row)->Get("host_name");
+ String service_description = static_cast<Dictionary::Ptr>(row)->Get("service_description");
+
+ if (service_description.IsEmpty() || host_name.IsEmpty())
+ return nullptr;
+
+ return Service::GetByNamePair(host_name, service_description);
+}
+
+Object::Ptr LogTable::ContactAccessor(const Value& row, const Column::ObjectAccessor&)
+{
+ String contact_name = static_cast<Dictionary::Ptr>(row)->Get("contact_name");
+
+ if (contact_name.IsEmpty())
+ return nullptr;
+
+ return User::GetByName(contact_name);
+}
+
+Object::Ptr LogTable::CommandAccessor(const Value& row, const Column::ObjectAccessor&)
+{
+ String command_name = static_cast<Dictionary::Ptr>(row)->Get("command_name");
+
+ if (command_name.IsEmpty())
+ return nullptr;
+
+ CheckCommand::Ptr check_command = CheckCommand::GetByName(command_name);
+ if (!check_command) {
+ EventCommand::Ptr event_command = EventCommand::GetByName(command_name);
+ if (!event_command) {
+ NotificationCommand::Ptr notification_command = NotificationCommand::GetByName(command_name);
+ if (!notification_command)
+ return nullptr;
+ else
+ return notification_command;
+ } else
+ return event_command;
+ } else
+ return check_command;
+}
+
+Value LogTable::TimeAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("time");
+}
+
+Value LogTable::LinenoAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("lineno");
+}
+
+Value LogTable::ClassAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("class");
+}
+
+Value LogTable::MessageAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("message");
+}
+
+Value LogTable::TypeAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("type");
+}
+
+Value LogTable::OptionsAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("options");
+}
+
+Value LogTable::CommentAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("comment");
+}
+
+Value LogTable::PluginOutputAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("plugin_output");
+}
+
+Value LogTable::StateAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("state");
+}
+
+Value LogTable::StateTypeAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("state_type");
+}
+
+Value LogTable::AttemptAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("attempt");
+}
+
+Value LogTable::ServiceDescriptionAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("service_description");
+}
+
+Value LogTable::HostNameAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("host_name");
+}
+
+Value LogTable::ContactNameAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("contact_name");
+}
+
+Value LogTable::CommandNameAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("command_name");
+}
diff --git a/lib/livestatus/logtable.hpp b/lib/livestatus/logtable.hpp
new file mode 100644
index 0000000..7a89310
--- /dev/null
+++ b/lib/livestatus/logtable.hpp
@@ -0,0 +1,65 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef LOGTABLE_H
+#define LOGTABLE_H
+
+#include "livestatus/historytable.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class LogTable final : public HistoryTable
+{
+public:
+ DECLARE_PTR_TYPEDEFS(LogTable);
+
+ LogTable(const String& compat_log_path, time_t from, time_t until);
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+ void UpdateLogEntries(const Dictionary::Ptr& log_entry_attrs, int line_count, int lineno, const AddRowFunction& addRowFn) override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Object::Ptr HostAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+ static Object::Ptr ServiceAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+ static Object::Ptr ContactAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+ static Object::Ptr CommandAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+
+ static Value TimeAccessor(const Value& row);
+ static Value LinenoAccessor(const Value& row);
+ static Value ClassAccessor(const Value& row);
+ static Value MessageAccessor(const Value& row);
+ static Value TypeAccessor(const Value& row);
+ static Value OptionsAccessor(const Value& row);
+ static Value CommentAccessor(const Value& row);
+ static Value PluginOutputAccessor(const Value& row);
+ static Value StateAccessor(const Value& row);
+ static Value StateTypeAccessor(const Value& row);
+ static Value AttemptAccessor(const Value& row);
+ static Value ServiceDescriptionAccessor(const Value& row);
+ static Value HostNameAccessor(const Value& row);
+ static Value ContactNameAccessor(const Value& row);
+ static Value CommandNameAccessor(const Value& row);
+
+private:
+ std::map<time_t, String> m_LogFileIndex;
+ std::map<time_t, Dictionary::Ptr> m_RowsCache;
+ time_t m_TimeFrom;
+ time_t m_TimeUntil;
+ String m_CompatLogPath;
+};
+
+}
+
+#endif /* LOGTABLE_H */
diff --git a/lib/livestatus/maxaggregator.cpp b/lib/livestatus/maxaggregator.cpp
new file mode 100644
index 0000000..375d24b
--- /dev/null
+++ b/lib/livestatus/maxaggregator.cpp
@@ -0,0 +1,38 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/maxaggregator.hpp"
+
+using namespace icinga;
+
+MaxAggregator::MaxAggregator(String attr)
+ : m_MaxAttr(std::move(attr))
+{ }
+
+MaxAggregatorState *MaxAggregator::EnsureState(AggregatorState **state)
+{
+ if (!*state)
+ *state = new MaxAggregatorState();
+
+ return static_cast<MaxAggregatorState *>(*state);
+}
+
+void MaxAggregator::Apply(const Table::Ptr& table, const Value& row, AggregatorState **state)
+{
+ Column column = table->GetColumn(m_MaxAttr);
+
+ Value value = column.ExtractValue(row);
+
+ MaxAggregatorState *pstate = EnsureState(state);
+
+ if (value > pstate->Max)
+ pstate->Max = value;
+}
+
+double MaxAggregator::GetResultAndFreeState(AggregatorState *state) const
+{
+ MaxAggregatorState *pstate = EnsureState(&state);
+ double result = pstate->Max;
+ delete pstate;
+
+ return result;
+}
diff --git a/lib/livestatus/maxaggregator.hpp b/lib/livestatus/maxaggregator.hpp
new file mode 100644
index 0000000..5bff5f9
--- /dev/null
+++ b/lib/livestatus/maxaggregator.hpp
@@ -0,0 +1,41 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef MAXAGGREGATOR_H
+#define MAXAGGREGATOR_H
+
+#include "livestatus/table.hpp"
+#include "livestatus/aggregator.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+struct MaxAggregatorState final : public AggregatorState
+{
+ double Max{0};
+};
+
+/**
+ * @ingroup livestatus
+ */
+class MaxAggregator final : public Aggregator
+{
+public:
+ DECLARE_PTR_TYPEDEFS(MaxAggregator);
+
+ MaxAggregator(String attr);
+
+ void Apply(const Table::Ptr& table, const Value& row, AggregatorState **state) override;
+ double GetResultAndFreeState(AggregatorState *state) const override;
+
+private:
+ String m_MaxAttr;
+
+ static MaxAggregatorState *EnsureState(AggregatorState **state);
+};
+
+}
+
+#endif /* MAXAGGREGATOR_H */
diff --git a/lib/livestatus/minaggregator.cpp b/lib/livestatus/minaggregator.cpp
new file mode 100644
index 0000000..06cb76e
--- /dev/null
+++ b/lib/livestatus/minaggregator.cpp
@@ -0,0 +1,45 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/minaggregator.hpp"
+
+using namespace icinga;
+
+MinAggregator::MinAggregator(String attr)
+ : m_MinAttr(std::move(attr))
+{ }
+
+MinAggregatorState *MinAggregator::EnsureState(AggregatorState **state)
+{
+ if (!*state)
+ *state = new MinAggregatorState();
+
+ return static_cast<MinAggregatorState *>(*state);
+}
+
+void MinAggregator::Apply(const Table::Ptr& table, const Value& row, AggregatorState **state)
+{
+ Column column = table->GetColumn(m_MinAttr);
+
+ Value value = column.ExtractValue(row);
+
+ MinAggregatorState *pstate = EnsureState(state);
+
+ if (value < pstate->Min)
+ pstate->Min = value;
+}
+
+double MinAggregator::GetResultAndFreeState(AggregatorState *state) const
+{
+ MinAggregatorState *pstate = EnsureState(&state);
+
+ double result;
+
+ if (pstate->Min == DBL_MAX)
+ result = 0;
+ else
+ result = pstate->Min;
+
+ delete pstate;
+
+ return result;
+}
diff --git a/lib/livestatus/minaggregator.hpp b/lib/livestatus/minaggregator.hpp
new file mode 100644
index 0000000..71a9d89
--- /dev/null
+++ b/lib/livestatus/minaggregator.hpp
@@ -0,0 +1,42 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef MINAGGREGATOR_H
+#define MINAGGREGATOR_H
+
+#include "livestatus/table.hpp"
+#include "livestatus/aggregator.hpp"
+#include <cfloat>
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+struct MinAggregatorState final : public AggregatorState
+{
+ double Min{DBL_MAX};
+};
+
+/**
+ * @ingroup livestatus
+ */
+class MinAggregator final : public Aggregator
+{
+public:
+ DECLARE_PTR_TYPEDEFS(MinAggregator);
+
+ MinAggregator(String attr);
+
+ void Apply(const Table::Ptr& table, const Value& row, AggregatorState **state) override;
+ double GetResultAndFreeState(AggregatorState *state) const override;
+
+private:
+ String m_MinAttr;
+
+ static MinAggregatorState *EnsureState(AggregatorState **state);
+};
+
+}
+
+#endif /* MINAGGREGATOR_H */
diff --git a/lib/livestatus/negatefilter.cpp b/lib/livestatus/negatefilter.cpp
new file mode 100644
index 0000000..60202b4
--- /dev/null
+++ b/lib/livestatus/negatefilter.cpp
@@ -0,0 +1,14 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/negatefilter.hpp"
+
+using namespace icinga;
+
+NegateFilter::NegateFilter(Filter::Ptr inner)
+ : m_Inner(std::move(inner))
+{ }
+
+bool NegateFilter::Apply(const Table::Ptr& table, const Value& row)
+{
+ return !m_Inner->Apply(table, row);
+}
diff --git a/lib/livestatus/negatefilter.hpp b/lib/livestatus/negatefilter.hpp
new file mode 100644
index 0000000..c08943c
--- /dev/null
+++ b/lib/livestatus/negatefilter.hpp
@@ -0,0 +1,31 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef NEGATEFILTER_H
+#define NEGATEFILTER_H
+
+#include "livestatus/filter.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class NegateFilter final : public Filter
+{
+public:
+ DECLARE_PTR_TYPEDEFS(NegateFilter);
+
+ NegateFilter(Filter::Ptr inner);
+
+ bool Apply(const Table::Ptr& table, const Value& row) override;
+
+private:
+ Filter::Ptr m_Inner;
+};
+
+}
+
+#endif /* NEGATEFILTER_H */
diff --git a/lib/livestatus/orfilter.cpp b/lib/livestatus/orfilter.cpp
new file mode 100644
index 0000000..6cc446c
--- /dev/null
+++ b/lib/livestatus/orfilter.cpp
@@ -0,0 +1,18 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/orfilter.hpp"
+
+using namespace icinga;
+
+bool OrFilter::Apply(const Table::Ptr& table, const Value& row)
+{
+ if (m_Filters.empty())
+ return true;
+
+ for (const Filter::Ptr& filter : m_Filters) {
+ if (filter->Apply(table, row))
+ return true;
+ }
+
+ return false;
+}
diff --git a/lib/livestatus/orfilter.hpp b/lib/livestatus/orfilter.hpp
new file mode 100644
index 0000000..df855c1
--- /dev/null
+++ b/lib/livestatus/orfilter.hpp
@@ -0,0 +1,26 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef ORFILTER_H
+#define ORFILTER_H
+
+#include "livestatus/combinerfilter.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class OrFilter final : public CombinerFilter
+{
+public:
+ DECLARE_PTR_TYPEDEFS(OrFilter);
+
+ bool Apply(const Table::Ptr& table, const Value& row) override;
+};
+
+}
+
+#endif /* ORFILTER_H */
diff --git a/lib/livestatus/servicegroupstable.cpp b/lib/livestatus/servicegroupstable.cpp
new file mode 100644
index 0000000..38d6d05
--- /dev/null
+++ b/lib/livestatus/servicegroupstable.cpp
@@ -0,0 +1,323 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/servicegroupstable.hpp"
+#include "icinga/servicegroup.hpp"
+#include "base/configtype.hpp"
+
+using namespace icinga;
+
+ServiceGroupsTable::ServiceGroupsTable()
+{
+ AddColumns(this);
+}
+
+void ServiceGroupsTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "name", Column(&ServiceGroupsTable::NameAccessor, objectAccessor));
+ table->AddColumn(prefix + "alias", Column(&ServiceGroupsTable::AliasAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes", Column(&ServiceGroupsTable::NotesAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes_url", Column(&ServiceGroupsTable::NotesUrlAccessor, objectAccessor));
+ table->AddColumn(prefix + "action_url", Column(&ServiceGroupsTable::ActionUrlAccessor, objectAccessor));
+ table->AddColumn(prefix + "members", Column(&ServiceGroupsTable::MembersAccessor, objectAccessor));
+ table->AddColumn(prefix + "members_with_state", Column(&ServiceGroupsTable::MembersWithStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "worst_service_state", Column(&ServiceGroupsTable::WorstServiceStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services", Column(&ServiceGroupsTable::NumServicesAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_ok", Column(&ServiceGroupsTable::NumServicesOkAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_warn", Column(&ServiceGroupsTable::NumServicesWarnAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_crit", Column(&ServiceGroupsTable::NumServicesCritAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_unknown", Column(&ServiceGroupsTable::NumServicesUnknownAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_pending", Column(&ServiceGroupsTable::NumServicesPendingAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_ok", Column(&ServiceGroupsTable::NumServicesHardOkAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_warn", Column(&ServiceGroupsTable::NumServicesHardWarnAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_crit", Column(&ServiceGroupsTable::NumServicesHardCritAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services_hard_unknown", Column(&ServiceGroupsTable::NumServicesHardUnknownAccessor, objectAccessor));
+}
+
+String ServiceGroupsTable::GetName() const
+{
+ return "servicegroups";
+}
+
+String ServiceGroupsTable::GetPrefix() const
+{
+ return "servicegroup";
+}
+
+void ServiceGroupsTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ for (const ServiceGroup::Ptr& sg : ConfigType::GetObjectsByType<ServiceGroup>()) {
+ if (!addRowFn(sg, LivestatusGroupByNone, Empty))
+ return;
+ }
+}
+
+Value ServiceGroupsTable::NameAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ return sg->GetName();
+}
+
+Value ServiceGroupsTable::AliasAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ return sg->GetDisplayName();
+}
+
+Value ServiceGroupsTable::NotesAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ return sg->GetNotes();
+}
+
+Value ServiceGroupsTable::NotesUrlAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ return sg->GetNotesUrl();
+}
+
+Value ServiceGroupsTable::ActionUrlAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ return sg->GetActionUrl();
+}
+
+Value ServiceGroupsTable::MembersAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ result.push_back(new Array({
+ service->GetHost()->GetName(),
+ service->GetShortName()
+ }));
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServiceGroupsTable::MembersWithStateAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ result.push_back(new Array({
+ service->GetHost()->GetName(),
+ service->GetShortName(),
+ service->GetHost()->GetState(),
+ service->GetState()
+ }));
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServiceGroupsTable::WorstServiceStateAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ Value worst_service = ServiceOK;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ if (service->GetState() > worst_service)
+ worst_service = service->GetState();
+ }
+
+ return worst_service;
+}
+
+Value ServiceGroupsTable::NumServicesAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ return sg->GetMembers().size();
+}
+
+Value ServiceGroupsTable::NumServicesOkAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ if (service->GetState() == ServiceOK)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value ServiceGroupsTable::NumServicesWarnAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ if (service->GetState() == ServiceWarning)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value ServiceGroupsTable::NumServicesCritAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ if (service->GetState() == ServiceCritical)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value ServiceGroupsTable::NumServicesUnknownAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ if (service->GetState() == ServiceUnknown)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value ServiceGroupsTable::NumServicesPendingAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ if (!service->GetLastCheckResult())
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value ServiceGroupsTable::NumServicesHardOkAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceOK)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value ServiceGroupsTable::NumServicesHardWarnAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceWarning)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value ServiceGroupsTable::NumServicesHardCritAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceCritical)
+ num_services++;
+ }
+
+ return num_services;
+}
+
+Value ServiceGroupsTable::NumServicesHardUnknownAccessor(const Value& row)
+{
+ ServiceGroup::Ptr sg = static_cast<ServiceGroup::Ptr>(row);
+
+ if (!sg)
+ return Empty;
+
+ int num_services = 0;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ if (service->GetStateType() == StateTypeHard && service->GetState() == ServiceUnknown)
+ num_services++;
+ }
+
+ return num_services;
+}
diff --git a/lib/livestatus/servicegroupstable.hpp b/lib/livestatus/servicegroupstable.hpp
new file mode 100644
index 0000000..b3c60c4
--- /dev/null
+++ b/lib/livestatus/servicegroupstable.hpp
@@ -0,0 +1,54 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef SERVICEGROUPSTABLE_H
+#define SERVICEGROUPSTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class ServiceGroupsTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ServiceGroupsTable);
+
+ ServiceGroupsTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Value NameAccessor(const Value& row);
+ static Value AliasAccessor(const Value& row);
+ static Value NotesAccessor(const Value& row);
+ static Value NotesUrlAccessor(const Value& row);
+ static Value ActionUrlAccessor(const Value& row);
+ static Value MembersAccessor(const Value& row);
+ static Value MembersWithStateAccessor(const Value& row);
+ static Value WorstServiceStateAccessor(const Value& row);
+ static Value NumServicesAccessor(const Value& row);
+ static Value NumServicesOkAccessor(const Value& row);
+ static Value NumServicesWarnAccessor(const Value& row);
+ static Value NumServicesCritAccessor(const Value& row);
+ static Value NumServicesUnknownAccessor(const Value& row);
+ static Value NumServicesPendingAccessor(const Value& row);
+ static Value NumServicesHardOkAccessor(const Value& row);
+ static Value NumServicesHardWarnAccessor(const Value& row);
+ static Value NumServicesHardCritAccessor(const Value& row);
+ static Value NumServicesHardUnknownAccessor(const Value& row);
+};
+
+}
+
+#endif /* SERVICEGROUPSTABLE_H */
diff --git a/lib/livestatus/servicestable.cpp b/lib/livestatus/servicestable.cpp
new file mode 100644
index 0000000..681445a
--- /dev/null
+++ b/lib/livestatus/servicestable.cpp
@@ -0,0 +1,1200 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/servicestable.hpp"
+#include "livestatus/hoststable.hpp"
+#include "livestatus/servicegroupstable.hpp"
+#include "livestatus/hostgroupstable.hpp"
+#include "livestatus/endpointstable.hpp"
+#include "icinga/service.hpp"
+#include "icinga/servicegroup.hpp"
+#include "icinga/hostgroup.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/timeperiod.hpp"
+#include "icinga/macroprocessor.hpp"
+#include "icinga/compatutility.hpp"
+#include "icinga/pluginutility.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/json.hpp"
+#include "base/convert.hpp"
+#include "base/utility.hpp"
+#include <boost/algorithm/string/replace.hpp>
+
+using namespace icinga;
+
+ServicesTable::ServicesTable(LivestatusGroupByType type)
+ : Table(type)
+{
+ AddColumns(this);
+}
+
+
+void ServicesTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "description", Column(&ServicesTable::ShortNameAccessor, objectAccessor));
+ table->AddColumn(prefix + "service_description", Column(&ServicesTable::ShortNameAccessor, objectAccessor)); //ugly compatibility hack
+ table->AddColumn(prefix + "display_name", Column(&ServicesTable::DisplayNameAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_command", Column(&ServicesTable::CheckCommandAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_command_expanded", Column(&ServicesTable::CheckCommandExpandedAccessor, objectAccessor));
+ table->AddColumn(prefix + "event_handler", Column(&ServicesTable::EventHandlerAccessor, objectAccessor));
+ table->AddColumn(prefix + "plugin_output", Column(&ServicesTable::PluginOutputAccessor, objectAccessor));
+ table->AddColumn(prefix + "long_plugin_output", Column(&ServicesTable::LongPluginOutputAccessor, objectAccessor));
+ table->AddColumn(prefix + "perf_data", Column(&ServicesTable::PerfDataAccessor, objectAccessor));
+ table->AddColumn(prefix + "notification_period", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_period", Column(&ServicesTable::CheckPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes", Column(&ServicesTable::NotesAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes_expanded", Column(&ServicesTable::NotesExpandedAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes_url", Column(&ServicesTable::NotesUrlAccessor, objectAccessor));
+ table->AddColumn(prefix + "notes_url_expanded", Column(&ServicesTable::NotesUrlExpandedAccessor, objectAccessor));
+ table->AddColumn(prefix + "action_url", Column(&ServicesTable::ActionUrlAccessor, objectAccessor));
+ table->AddColumn(prefix + "action_url_expanded", Column(&ServicesTable::ActionUrlExpandedAccessor, objectAccessor));
+ table->AddColumn(prefix + "icon_image", Column(&ServicesTable::IconImageAccessor, objectAccessor));
+ table->AddColumn(prefix + "icon_image_expanded", Column(&ServicesTable::IconImageExpandedAccessor, objectAccessor));
+ table->AddColumn(prefix + "icon_image_alt", Column(&ServicesTable::IconImageAltAccessor, objectAccessor));
+ table->AddColumn(prefix + "initial_state", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "max_check_attempts", Column(&ServicesTable::MaxCheckAttemptsAccessor, objectAccessor));
+ table->AddColumn(prefix + "current_attempt", Column(&ServicesTable::CurrentAttemptAccessor, objectAccessor));
+ table->AddColumn(prefix + "state", Column(&ServicesTable::StateAccessor, objectAccessor));
+ table->AddColumn(prefix + "has_been_checked", Column(&ServicesTable::HasBeenCheckedAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_state", Column(&ServicesTable::LastStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_hard_state", Column(&ServicesTable::LastHardStateAccessor, objectAccessor));
+ table->AddColumn(prefix + "state_type", Column(&ServicesTable::StateTypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_type", Column(&ServicesTable::CheckTypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "acknowledged", Column(&ServicesTable::AcknowledgedAccessor, objectAccessor));
+ table->AddColumn(prefix + "acknowledgement_type", Column(&ServicesTable::AcknowledgementTypeAccessor, objectAccessor));
+ table->AddColumn(prefix + "no_more_notifications", Column(&ServicesTable::NoMoreNotificationsAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_time_ok", Column(&ServicesTable::LastTimeOkAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_time_warning", Column(&ServicesTable::LastTimeWarningAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_time_critical", Column(&ServicesTable::LastTimeCriticalAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_time_unknown", Column(&ServicesTable::LastTimeUnknownAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_check", Column(&ServicesTable::LastCheckAccessor, objectAccessor));
+ table->AddColumn(prefix + "next_check", Column(&ServicesTable::NextCheckAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_notification", Column(&ServicesTable::LastNotificationAccessor, objectAccessor));
+ table->AddColumn(prefix + "next_notification", Column(&ServicesTable::NextNotificationAccessor, objectAccessor));
+ table->AddColumn(prefix + "current_notification_number", Column(&ServicesTable::CurrentNotificationNumberAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_state_change", Column(&ServicesTable::LastStateChangeAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_hard_state_change", Column(&ServicesTable::LastHardStateChangeAccessor, objectAccessor));
+ table->AddColumn(prefix + "scheduled_downtime_depth", Column(&ServicesTable::ScheduledDowntimeDepthAccessor, objectAccessor));
+ table->AddColumn(prefix + "is_flapping", Column(&ServicesTable::IsFlappingAccessor, objectAccessor));
+ table->AddColumn(prefix + "checks_enabled", Column(&ServicesTable::ChecksEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "accept_passive_checks", Column(&ServicesTable::AcceptPassiveChecksAccessor, objectAccessor));
+ table->AddColumn(prefix + "event_handler_enabled", Column(&ServicesTable::EventHandlerEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "notifications_enabled", Column(&ServicesTable::NotificationsEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "process_performance_data", Column(&ServicesTable::ProcessPerformanceDataAccessor, objectAccessor));
+ table->AddColumn(prefix + "is_executing", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "active_checks_enabled", Column(&ServicesTable::ActiveChecksEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_options", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "flap_detection_enabled", Column(&ServicesTable::FlapDetectionEnabledAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_freshness", Column(&Table::OneAccessor, objectAccessor));
+ table->AddColumn(prefix + "obsess_over_service", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "modified_attributes", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "modified_attributes_list", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "pnpgraph_present", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "staleness", Column(&ServicesTable::StalenessAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_interval", Column(&ServicesTable::CheckIntervalAccessor, objectAccessor));
+ table->AddColumn(prefix + "retry_interval", Column(&ServicesTable::RetryIntervalAccessor, objectAccessor));
+ table->AddColumn(prefix + "notification_interval", Column(&ServicesTable::NotificationIntervalAccessor, objectAccessor));
+ table->AddColumn(prefix + "first_notification_delay", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "low_flap_threshold", Column(&ServicesTable::LowFlapThresholdAccessor, objectAccessor));
+ table->AddColumn(prefix + "high_flap_threshold", Column(&ServicesTable::HighFlapThresholdAccessor, objectAccessor));
+ table->AddColumn(prefix + "latency", Column(&ServicesTable::LatencyAccessor, objectAccessor));
+ table->AddColumn(prefix + "execution_time", Column(&ServicesTable::ExecutionTimeAccessor, objectAccessor));
+ table->AddColumn(prefix + "percent_state_change", Column(&ServicesTable::PercentStateChangeAccessor, objectAccessor));
+ table->AddColumn(prefix + "in_check_period", Column(&ServicesTable::InCheckPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "in_notification_period", Column(&ServicesTable::InNotificationPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "contacts", Column(&ServicesTable::ContactsAccessor, objectAccessor));
+ table->AddColumn(prefix + "downtimes", Column(&ServicesTable::DowntimesAccessor, objectAccessor));
+ table->AddColumn(prefix + "downtimes_with_info", Column(&ServicesTable::DowntimesWithInfoAccessor, objectAccessor));
+ table->AddColumn(prefix + "comments", Column(&ServicesTable::CommentsAccessor, objectAccessor));
+ table->AddColumn(prefix + "comments_with_info", Column(&ServicesTable::CommentsWithInfoAccessor, objectAccessor));
+ table->AddColumn(prefix + "comments_with_extra_info", Column(&ServicesTable::CommentsWithExtraInfoAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variable_names", Column(&ServicesTable::CustomVariableNamesAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variable_values", Column(&ServicesTable::CustomVariableValuesAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variables", Column(&ServicesTable::CustomVariablesAccessor, objectAccessor));
+ table->AddColumn(prefix + "groups", Column(&ServicesTable::GroupsAccessor, objectAccessor));
+ table->AddColumn(prefix + "contact_groups", Column(&ServicesTable::ContactGroupsAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_source", Column(&ServicesTable::CheckSourceAccessor, objectAccessor));
+ table->AddColumn(prefix + "is_reachable", Column(&ServicesTable::IsReachableAccessor, objectAccessor));
+ table->AddColumn(prefix + "cv_is_json", Column(&ServicesTable::CVIsJsonAccessor, objectAccessor));
+ table->AddColumn(prefix + "original_attributes", Column(&ServicesTable::OriginalAttributesAccessor, objectAccessor));
+
+ HostsTable::AddColumns(table, "host_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return HostAccessor(row, objectAccessor);
+ });
+
+ /* add additional group by values received through the object accessor */
+ if (table->GetGroupByType() == LivestatusGroupByServiceGroup) {
+ /* _1 = row, _2 = groupByType, _3 = groupByObject */
+ Log(LogDebug, "Livestatus")
+ << "Processing services group by servicegroup table.";
+ ServiceGroupsTable::AddColumns(table, "servicegroup_", [](const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject) -> Value {
+ return ServiceGroupAccessor(row, groupByType, groupByObject);
+ });
+ } else if (table->GetGroupByType() == LivestatusGroupByHostGroup) {
+ /* _1 = row, _2 = groupByType, _3 = groupByObject */
+ Log(LogDebug, "Livestatus")
+ << "Processing services group by hostgroup table.";
+ HostGroupsTable::AddColumns(table, "hostgroup_", [](const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject) -> Value {
+ return HostGroupAccessor(row, groupByType, groupByObject);
+ });
+ }
+}
+
+String ServicesTable::GetName() const
+{
+ return "services";
+}
+
+String ServicesTable::GetPrefix() const
+{
+ return "service";
+}
+
+void ServicesTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ if (GetGroupByType() == LivestatusGroupByServiceGroup) {
+ for (const ServiceGroup::Ptr& sg : ConfigType::GetObjectsByType<ServiceGroup>()) {
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ /* the caller must know which groupby type and value are set for this row */
+ if (!addRowFn(service, LivestatusGroupByServiceGroup, sg))
+ return;
+ }
+ }
+ } else if (GetGroupByType() == LivestatusGroupByHostGroup) {
+ for (const HostGroup::Ptr& hg : ConfigType::GetObjectsByType<HostGroup>()) {
+ ObjectLock ylock(hg);
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ ObjectLock ylock(host);
+ for (const Service::Ptr& service : host->GetServices()) {
+ /* the caller must know which groupby type and value are set for this row */
+ if (!addRowFn(service, LivestatusGroupByHostGroup, hg))
+ return;
+ }
+ }
+ }
+ } else {
+ for (const Service::Ptr& service : ConfigType::GetObjectsByType<Service>()) {
+ if (!addRowFn(service, LivestatusGroupByNone, Empty))
+ return;
+ }
+ }
+}
+
+Object::Ptr ServicesTable::HostAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor)
+{
+ Value service;
+
+ if (parentObjectAccessor)
+ service = parentObjectAccessor(row, LivestatusGroupByNone, Empty);
+ else
+ service = row;
+
+ Service::Ptr svc = static_cast<Service::Ptr>(service);
+
+ if (!svc)
+ return nullptr;
+
+ return svc->GetHost();
+}
+
+Object::Ptr ServicesTable::ServiceGroupAccessor(const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject)
+{
+ /* return the current group by value set from within FetchRows()
+ * this is the servicegroup object used for the table join inside
+ * in AddColumns()
+ */
+ if (groupByType == LivestatusGroupByServiceGroup)
+ return groupByObject;
+
+ return nullptr;
+}
+
+Object::Ptr ServicesTable::HostGroupAccessor(const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject)
+{
+ /* return the current group by value set from within FetchRows()
+ * this is the servicegroup object used for the table join inside
+ * in AddColumns()
+ */
+ if (groupByType == LivestatusGroupByHostGroup)
+ return groupByObject;
+
+ return nullptr;
+}
+
+Value ServicesTable::ShortNameAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetShortName();
+}
+
+Value ServicesTable::DisplayNameAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetDisplayName();
+}
+
+Value ServicesTable::CheckCommandAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ CheckCommand::Ptr checkcommand = service->GetCheckCommand();
+
+ if (checkcommand)
+ return CompatUtility::GetCommandName(checkcommand) + "!" + CompatUtility::GetCheckableCommandArgs(service);
+
+ return Empty;
+}
+
+Value ServicesTable::CheckCommandExpandedAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ CheckCommand::Ptr checkcommand = service->GetCheckCommand();
+
+ if (checkcommand)
+ return CompatUtility::GetCommandName(checkcommand) + "!" + CompatUtility::GetCheckableCommandArgs(service);
+
+ return Empty;
+}
+
+Value ServicesTable::EventHandlerAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ EventCommand::Ptr eventcommand = service->GetEventCommand();
+
+ if (eventcommand)
+ return CompatUtility::GetCommandName(eventcommand);
+
+ return Empty;
+}
+
+Value ServicesTable::PluginOutputAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ String output;
+ CheckResult::Ptr cr = service->GetLastCheckResult();
+
+ if (cr)
+ output = CompatUtility::GetCheckResultOutput(cr);
+
+ return output;
+}
+
+Value ServicesTable::LongPluginOutputAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ String long_output;
+ CheckResult::Ptr cr = service->GetLastCheckResult();
+
+ if (cr)
+ long_output = CompatUtility::GetCheckResultLongOutput(cr);
+
+ return long_output;
+}
+
+Value ServicesTable::PerfDataAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ String perfdata;
+ CheckResult::Ptr cr = service->GetLastCheckResult();
+
+ if (!cr)
+ return Empty;
+
+ return PluginUtility::FormatPerfdata(cr->GetPerformanceData());
+}
+
+Value ServicesTable::CheckPeriodAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ TimePeriod::Ptr checkPeriod = service->GetCheckPeriod();
+
+ if (!checkPeriod)
+ return Empty;
+
+ return checkPeriod->GetName();
+}
+
+Value ServicesTable::NotesAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetNotes();
+}
+
+Value ServicesTable::NotesExpandedAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ MacroProcessor::ResolverList resolvers {
+ { "service", service },
+ { "host", service->GetHost() },
+ };
+
+ return MacroProcessor::ResolveMacros(service->GetNotes(), resolvers);
+}
+
+Value ServicesTable::NotesUrlAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetNotesUrl();
+}
+
+Value ServicesTable::NotesUrlExpandedAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ MacroProcessor::ResolverList resolvers {
+ { "service", service },
+ { "host", service->GetHost() },
+ };
+
+ return MacroProcessor::ResolveMacros(service->GetNotesUrl(), resolvers);
+}
+
+Value ServicesTable::ActionUrlAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetActionUrl();
+}
+
+Value ServicesTable::ActionUrlExpandedAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ MacroProcessor::ResolverList resolvers {
+ { "service", service },
+ { "host", service->GetHost() },
+ };
+
+ return MacroProcessor::ResolveMacros(service->GetActionUrl(), resolvers);
+}
+
+Value ServicesTable::IconImageAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetIconImage();
+}
+
+Value ServicesTable::IconImageExpandedAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ MacroProcessor::ResolverList resolvers {
+ { "service", service },
+ { "host", service->GetHost() },
+ };
+
+ return MacroProcessor::ResolveMacros(service->GetIconImage(), resolvers);
+}
+
+Value ServicesTable::IconImageAltAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetIconImageAlt();
+}
+
+Value ServicesTable::MaxCheckAttemptsAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetMaxCheckAttempts();
+}
+
+Value ServicesTable::CurrentAttemptAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetCheckAttempt();
+}
+
+Value ServicesTable::StateAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetState();
+}
+
+Value ServicesTable::HasBeenCheckedAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return Convert::ToLong(service->HasBeenChecked());
+}
+
+Value ServicesTable::LastStateAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetLastState();
+}
+
+Value ServicesTable::LastHardStateAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetLastHardState();
+}
+
+Value ServicesTable::StateTypeAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetStateType();
+}
+
+Value ServicesTable::CheckTypeAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return (service->GetEnableActiveChecks() ? 0 : 1); /* 0 .. active, 1 .. passive */
+}
+
+Value ServicesTable::AcknowledgedAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ ObjectLock olock(service);
+ return service->IsAcknowledged();
+}
+
+Value ServicesTable::AcknowledgementTypeAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ ObjectLock olock(service);
+ return service->GetAcknowledgement();
+}
+
+Value ServicesTable::NoMoreNotificationsAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return (CompatUtility::GetCheckableNotificationNotificationInterval(service) == 0 && !service->GetVolatile()) ? 1 : 0;
+}
+
+Value ServicesTable::LastTimeOkAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return static_cast<int>(service->GetLastStateOK());
+}
+
+Value ServicesTable::LastTimeWarningAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return static_cast<int>(service->GetLastStateWarning());
+}
+
+Value ServicesTable::LastTimeCriticalAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return static_cast<int>(service->GetLastStateCritical());
+}
+
+Value ServicesTable::LastTimeUnknownAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return static_cast<int>(service->GetLastStateUnknown());
+}
+
+Value ServicesTable::LastCheckAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return static_cast<int>(service->GetLastCheck());
+}
+
+Value ServicesTable::NextCheckAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return static_cast<int>(service->GetNextCheck());
+}
+
+Value ServicesTable::LastNotificationAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return CompatUtility::GetCheckableNotificationLastNotification(service);
+}
+
+Value ServicesTable::NextNotificationAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return CompatUtility::GetCheckableNotificationNextNotification(service);
+}
+
+Value ServicesTable::CurrentNotificationNumberAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return CompatUtility::GetCheckableNotificationNotificationNumber(service);
+}
+
+Value ServicesTable::LastStateChangeAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return static_cast<int>(service->GetLastStateChange());
+}
+
+Value ServicesTable::LastHardStateChangeAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return static_cast<int>(service->GetLastHardStateChange());
+}
+
+Value ServicesTable::ScheduledDowntimeDepthAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetDowntimeDepth();
+}
+
+Value ServicesTable::IsFlappingAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->IsFlapping();
+}
+
+Value ServicesTable::ChecksEnabledAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return Convert::ToLong(service->GetEnableActiveChecks());
+}
+
+Value ServicesTable::AcceptPassiveChecksAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return Convert::ToLong(service->GetEnablePassiveChecks());
+}
+
+Value ServicesTable::EventHandlerEnabledAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return Convert::ToLong(service->GetEnableEventHandler());
+}
+
+Value ServicesTable::NotificationsEnabledAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return Convert::ToLong(service->GetEnableNotifications());
+}
+
+Value ServicesTable::ProcessPerformanceDataAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return Convert::ToLong(service->GetEnablePerfdata());
+}
+
+Value ServicesTable::ActiveChecksEnabledAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return Convert::ToLong(service->GetEnableActiveChecks());
+}
+
+Value ServicesTable::FlapDetectionEnabledAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return Convert::ToLong(service->GetEnableFlapping());
+}
+
+Value ServicesTable::StalenessAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ if (service->HasBeenChecked() && service->GetLastCheck() > 0)
+ return (Utility::GetTime() - service->GetLastCheck()) / (service->GetCheckInterval() * 3600);
+
+ return 0.0;
+}
+
+Value ServicesTable::CheckIntervalAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetCheckInterval() / LIVESTATUS_INTERVAL_LENGTH;
+}
+
+Value ServicesTable::RetryIntervalAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetRetryInterval() / LIVESTATUS_INTERVAL_LENGTH;
+}
+
+Value ServicesTable::NotificationIntervalAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return CompatUtility::GetCheckableNotificationNotificationInterval(service);
+}
+
+Value ServicesTable::LowFlapThresholdAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetFlappingThresholdLow();
+}
+
+Value ServicesTable::HighFlapThresholdAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetFlappingThresholdHigh();
+}
+
+Value ServicesTable::LatencyAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ CheckResult::Ptr cr = service->GetLastCheckResult();
+
+ if (!cr)
+ return Empty;
+
+ return cr->CalculateLatency();
+}
+
+Value ServicesTable::ExecutionTimeAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ CheckResult::Ptr cr = service->GetLastCheckResult();
+
+ if (!cr)
+ return Empty;
+
+ return cr->CalculateExecutionTime();
+}
+
+Value ServicesTable::PercentStateChangeAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->GetFlappingCurrent();
+}
+
+Value ServicesTable::InCheckPeriodAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ TimePeriod::Ptr timeperiod = service->GetCheckPeriod();
+
+ /* none set means always checked */
+ if (!timeperiod)
+ return 1;
+
+ return Convert::ToLong(timeperiod->IsInside(Utility::GetTime()));
+}
+
+Value ServicesTable::InNotificationPeriodAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ for (const Notification::Ptr& notification : service->GetNotifications()) {
+ TimePeriod::Ptr timeperiod = notification->GetPeriod();
+
+ if (!timeperiod || timeperiod->IsInside(Utility::GetTime()))
+ return 1;
+ }
+
+ return 0;
+}
+
+Value ServicesTable::ContactsAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ ArrayData result;
+
+ for (const User::Ptr& user : CompatUtility::GetCheckableNotificationUsers(service)) {
+ result.push_back(user->GetName());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServicesTable::DowntimesAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Downtime::Ptr& downtime : service->GetDowntimes()) {
+ if (downtime->IsExpired())
+ continue;
+
+ result.push_back(downtime->GetLegacyId());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServicesTable::DowntimesWithInfoAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Downtime::Ptr& downtime : service->GetDowntimes()) {
+ if (downtime->IsExpired())
+ continue;
+
+ result.push_back(new Array({
+ downtime->GetLegacyId(),
+ downtime->GetAuthor(),
+ downtime->GetComment()
+ }));
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServicesTable::CommentsAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Comment::Ptr& comment : service->GetComments()) {
+ if (comment->IsExpired())
+ continue;
+
+ result.push_back(comment->GetLegacyId());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServicesTable::CommentsWithInfoAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Comment::Ptr& comment : service->GetComments()) {
+ if (comment->IsExpired())
+ continue;
+
+ result.push_back(new Array({
+ comment->GetLegacyId(),
+ comment->GetAuthor(),
+ comment->GetText()
+ }));
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServicesTable::CommentsWithExtraInfoAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ ArrayData result;
+
+ for (const Comment::Ptr& comment : service->GetComments()) {
+ if (comment->IsExpired())
+ continue;
+
+ result.push_back(new Array({
+ comment->GetLegacyId(),
+ comment->GetAuthor(),
+ comment->GetText(),
+ comment->GetEntryType(),
+ static_cast<int>(comment->GetEntryTime())
+ }));
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServicesTable::CustomVariableNamesAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ Dictionary::Ptr vars = service->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ result.push_back(kv.first);
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServicesTable::CustomVariableValuesAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ Dictionary::Ptr vars = service->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>())
+ result.push_back(JsonEncode(kv.second));
+ else
+ result.push_back(kv.second);
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServicesTable::CustomVariablesAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ Dictionary::Ptr vars = service->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ Value val;
+
+ if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>())
+ val = JsonEncode(kv.second);
+ else
+ val = kv.second;
+
+ result.push_back(new Array({
+ kv.first,
+ val
+ }));
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServicesTable::CVIsJsonAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ Dictionary::Ptr vars = service->GetVars();
+
+ if (!vars)
+ return Empty;
+
+ bool cv_is_json = false;
+
+ ObjectLock olock(vars);
+ for (const Dictionary::Pair& kv : vars) {
+ if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>())
+ cv_is_json = true;
+ }
+
+ return cv_is_json;
+}
+
+Value ServicesTable::GroupsAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ Array::Ptr groups = service->GetGroups();
+
+ if (!groups)
+ return Empty;
+
+ return groups;
+}
+
+Value ServicesTable::ContactGroupsAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ ArrayData result;
+
+ for (const UserGroup::Ptr& usergroup : CompatUtility::GetCheckableNotificationUserGroups(service)) {
+ result.push_back(usergroup->GetName());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ServicesTable::CheckSourceAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ CheckResult::Ptr cr = service->GetLastCheckResult();
+
+ if (cr)
+ return cr->GetCheckSource();
+
+ return Empty;
+}
+
+Value ServicesTable::IsReachableAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return service->IsReachable();
+}
+
+Value ServicesTable::OriginalAttributesAccessor(const Value& row)
+{
+ Service::Ptr service = static_cast<Service::Ptr>(row);
+
+ if (!service)
+ return Empty;
+
+ return JsonEncode(service->GetOriginalAttributes());
+}
diff --git a/lib/livestatus/servicestable.hpp b/lib/livestatus/servicestable.hpp
new file mode 100644
index 0000000..56d5ae5
--- /dev/null
+++ b/lib/livestatus/servicestable.hpp
@@ -0,0 +1,115 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef SERVICESTABLE_H
+#define SERVICESTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class ServicesTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ServicesTable);
+
+ ServicesTable(LivestatusGroupByType type = LivestatusGroupByNone);
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Object::Ptr HostAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+ static Object::Ptr ServiceGroupAccessor(const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject);
+ static Object::Ptr HostGroupAccessor(const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject);
+
+ static Value ShortNameAccessor(const Value& row);
+ static Value DisplayNameAccessor(const Value& row);
+ static Value CheckCommandAccessor(const Value& row);
+ static Value CheckCommandExpandedAccessor(const Value& row);
+ static Value EventHandlerAccessor(const Value& row);
+ static Value PluginOutputAccessor(const Value& row);
+ static Value LongPluginOutputAccessor(const Value& row);
+ static Value PerfDataAccessor(const Value& row);
+ static Value CheckPeriodAccessor(const Value& row);
+ static Value NotesAccessor(const Value& row);
+ static Value NotesExpandedAccessor(const Value& row);
+ static Value NotesUrlAccessor(const Value& row);
+ static Value NotesUrlExpandedAccessor(const Value& row);
+ static Value ActionUrlAccessor(const Value& row);
+ static Value ActionUrlExpandedAccessor(const Value& row);
+ static Value IconImageAccessor(const Value& row);
+ static Value IconImageExpandedAccessor(const Value& row);
+ static Value IconImageAltAccessor(const Value& row);
+ static Value MaxCheckAttemptsAccessor(const Value& row);
+ static Value CurrentAttemptAccessor(const Value& row);
+ static Value StateAccessor(const Value& row);
+ static Value HasBeenCheckedAccessor(const Value& row);
+ static Value LastStateAccessor(const Value& row);
+ static Value LastHardStateAccessor(const Value& row);
+ static Value StateTypeAccessor(const Value& row);
+ static Value CheckTypeAccessor(const Value& row);
+ static Value AcknowledgedAccessor(const Value& row);
+ static Value AcknowledgementTypeAccessor(const Value& row);
+ static Value NoMoreNotificationsAccessor(const Value& row);
+ static Value LastTimeOkAccessor(const Value& row);
+ static Value LastTimeWarningAccessor(const Value& row);
+ static Value LastTimeCriticalAccessor(const Value& row);
+ static Value LastTimeUnknownAccessor(const Value& row);
+ static Value LastCheckAccessor(const Value& row);
+ static Value NextCheckAccessor(const Value& row);
+ static Value LastNotificationAccessor(const Value& row);
+ static Value NextNotificationAccessor(const Value& row);
+ static Value CurrentNotificationNumberAccessor(const Value& row);
+ static Value LastStateChangeAccessor(const Value& row);
+ static Value LastHardStateChangeAccessor(const Value& row);
+ static Value ScheduledDowntimeDepthAccessor(const Value& row);
+ static Value IsFlappingAccessor(const Value& row);
+ static Value ChecksEnabledAccessor(const Value& row);
+ static Value AcceptPassiveChecksAccessor(const Value& row);
+ static Value EventHandlerEnabledAccessor(const Value& row);
+ static Value NotificationsEnabledAccessor(const Value& row);
+ static Value ProcessPerformanceDataAccessor(const Value& row);
+ static Value ActiveChecksEnabledAccessor(const Value& row);
+ static Value FlapDetectionEnabledAccessor(const Value& row);
+ static Value StalenessAccessor(const Value& row);
+ static Value CheckIntervalAccessor(const Value& row);
+ static Value RetryIntervalAccessor(const Value& row);
+ static Value NotificationIntervalAccessor(const Value& row);
+ static Value LowFlapThresholdAccessor(const Value& row);
+ static Value HighFlapThresholdAccessor(const Value& row);
+ static Value LatencyAccessor(const Value& row);
+ static Value ExecutionTimeAccessor(const Value& row);
+ static Value PercentStateChangeAccessor(const Value& row);
+ static Value InCheckPeriodAccessor(const Value& row);
+ static Value InNotificationPeriodAccessor(const Value& row);
+ static Value ContactsAccessor(const Value& row);
+ static Value DowntimesAccessor(const Value& row);
+ static Value DowntimesWithInfoAccessor(const Value& row);
+ static Value CommentsAccessor(const Value& row);
+ static Value CommentsWithInfoAccessor(const Value& row);
+ static Value CommentsWithExtraInfoAccessor(const Value& row);
+ static Value CustomVariableNamesAccessor(const Value& row);
+ static Value CustomVariableValuesAccessor(const Value& row);
+ static Value CustomVariablesAccessor(const Value& row);
+ static Value GroupsAccessor(const Value& row);
+ static Value ContactGroupsAccessor(const Value& row);
+ static Value CheckSourceAccessor(const Value& row);
+ static Value IsReachableAccessor(const Value& row);
+ static Value CVIsJsonAccessor(const Value& row);
+ static Value OriginalAttributesAccessor(const Value& row);
+};
+
+}
+
+#endif /* SERVICESTABLE_H */
diff --git a/lib/livestatus/statehisttable.cpp b/lib/livestatus/statehisttable.cpp
new file mode 100644
index 0000000..2d7e49b
--- /dev/null
+++ b/lib/livestatus/statehisttable.cpp
@@ -0,0 +1,466 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/statehisttable.hpp"
+#include "livestatus/livestatuslogutility.hpp"
+#include "livestatus/hoststable.hpp"
+#include "livestatus/servicestable.hpp"
+#include "livestatus/contactstable.hpp"
+#include "livestatus/commandstable.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/cib.hpp"
+#include "icinga/service.hpp"
+#include "icinga/host.hpp"
+#include "icinga/user.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "base/convert.hpp"
+#include "base/utility.hpp"
+#include "base/logger.hpp"
+#include "base/application.hpp"
+#include "base/objectlock.hpp"
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <fstream>
+
+using namespace icinga;
+
+StateHistTable::StateHistTable(const String& compat_log_path, time_t from, time_t until)
+{
+ /* store attributes for FetchRows */
+ m_TimeFrom = from;
+ m_TimeUntil = until;
+ m_CompatLogPath = compat_log_path;
+
+ AddColumns(this);
+}
+
+void StateHistTable::UpdateLogEntries(const Dictionary::Ptr& log_entry_attrs, int line_count, int lineno, const AddRowFunction& addRowFn)
+{
+ unsigned int time = log_entry_attrs->Get("time");
+ String host_name = log_entry_attrs->Get("host_name");
+ String service_description = log_entry_attrs->Get("service_description");
+ unsigned long state = log_entry_attrs->Get("state");
+ int log_type = log_entry_attrs->Get("log_type");
+ String state_type = log_entry_attrs->Get("state_type"); //SOFT, HARD, STARTED, STOPPED, ...
+ String log_line = log_entry_attrs->Get("message"); /* use message from log table */
+
+ Checkable::Ptr checkable;
+
+ if (service_description.IsEmpty())
+ checkable = Host::GetByName(host_name);
+ else
+ checkable = Service::GetByNamePair(host_name, service_description);
+
+ /* invalid log line for state history */
+ if (!checkable)
+ return;
+
+ Array::Ptr state_hist_service_states;
+ Dictionary::Ptr state_hist_bag;
+ unsigned long query_part = m_TimeUntil - m_TimeFrom;
+
+ /* insert new service states array with values if not existing */
+ if (m_CheckablesCache.find(checkable) == m_CheckablesCache.end()) {
+
+ /* create new values */
+ state_hist_service_states = new Array();
+ state_hist_bag = new Dictionary();
+
+ Service::Ptr service = dynamic_pointer_cast<Service>(checkable);
+ Host::Ptr host;
+
+ if (service)
+ host = service->GetHost();
+ else
+ host = static_pointer_cast<Host>(checkable);
+
+ state_hist_bag->Set("host_name", host->GetName());
+
+ if (service)
+ state_hist_bag->Set("service_description", service->GetShortName());
+
+ state_hist_bag->Set("state", state);
+ state_hist_bag->Set("in_downtime", 0);
+ state_hist_bag->Set("in_host_downtime", 0);
+ state_hist_bag->Set("in_notification_period", 1); // assume "always"
+ state_hist_bag->Set("is_flapping", 0);
+ state_hist_bag->Set("time", time);
+ state_hist_bag->Set("lineno", lineno);
+ state_hist_bag->Set("log_output", log_line); /* complete line */
+ state_hist_bag->Set("from", time); /* starting at current timestamp */
+ state_hist_bag->Set("until", time); /* will be updated later on state change */
+ state_hist_bag->Set("query_part", query_part); /* required for _part calculations */
+
+ state_hist_service_states->Add(state_hist_bag);
+
+ Log(LogDebug, "StateHistTable")
+ << "statehist: Adding new object '" << checkable->GetName() << "' to services cache.";
+ } else {
+ state_hist_service_states = m_CheckablesCache[checkable];
+ state_hist_bag = state_hist_service_states->Get(state_hist_service_states->GetLength()-1); /* fetch latest state from history */
+
+ /* state duration */
+
+ /* determine service notifications notification_period and compare against current timestamp */
+ bool in_notification_period = true;
+ String notification_period_name;
+ for (const Notification::Ptr& notification : checkable->GetNotifications()) {
+ TimePeriod::Ptr notification_period = notification->GetPeriod();
+
+ if (notification_period) {
+ if (notification_period->IsInside(static_cast<double>(time)))
+ in_notification_period = true;
+ else
+ in_notification_period = false;
+
+ notification_period_name = notification_period->GetName(); // last one wins
+ } else
+ in_notification_period = true; // assume "always"
+ }
+
+ /* check for state changes, flapping & downtime start/end */
+ switch (log_type) {
+ case LogEntryTypeHostAlert:
+ case LogEntryTypeHostInitialState:
+ case LogEntryTypeHostCurrentState:
+ case LogEntryTypeServiceAlert:
+ case LogEntryTypeServiceInitialState:
+ case LogEntryTypeServiceCurrentState:
+ if (state != state_hist_bag->Get("state")) {
+ /* 1. seal old state_hist_bag */
+ state_hist_bag->Set("until", time); /* add until record for duration calculation */
+
+ /* 2. add new state_hist_bag */
+ Dictionary::Ptr state_hist_bag_new = new Dictionary();
+
+ state_hist_bag_new->Set("host_name", state_hist_bag->Get("host_name"));
+ state_hist_bag_new->Set("service_description", state_hist_bag->Get("service_description"));
+ state_hist_bag_new->Set("state", state);
+ state_hist_bag_new->Set("in_downtime", state_hist_bag->Get("in_downtime")); // keep value from previous state!
+ state_hist_bag_new->Set("in_host_downtime", state_hist_bag->Get("in_host_downtime")); // keep value from previous state!
+ state_hist_bag_new->Set("in_notification_period", (in_notification_period ? 1 : 0));
+ state_hist_bag_new->Set("notification_period", notification_period_name);
+ state_hist_bag_new->Set("is_flapping", state_hist_bag->Get("is_flapping")); // keep value from previous state!
+ state_hist_bag_new->Set("time", time);
+ state_hist_bag_new->Set("lineno", lineno);
+ state_hist_bag_new->Set("log_output", log_line); /* complete line */
+ state_hist_bag_new->Set("from", time); /* starting at current timestamp */
+ state_hist_bag_new->Set("until", time + 1); /* will be updated later */
+ state_hist_bag_new->Set("query_part", query_part);
+
+ state_hist_service_states->Add(state_hist_bag_new);
+
+ Log(LogDebug, "StateHistTable")
+ << "statehist: State change detected for object '" << checkable->GetName() << "' in '" << log_line << "'.";
+ }
+ break;
+ case LogEntryTypeHostFlapping:
+ case LogEntryTypeServiceFlapping:
+ if (state_type == "STARTED")
+ state_hist_bag->Set("is_flapping", 1);
+ else if (state_type == "STOPPED" || state_type == "DISABLED")
+ state_hist_bag->Set("is_flapping", 0);
+ break;
+ break;
+ case LogEntryTypeHostDowntimeAlert:
+ case LogEntryTypeServiceDowntimeAlert:
+ if (state_type == "STARTED") {
+ state_hist_bag->Set("in_downtime", 1);
+ if (log_type == LogEntryTypeHostDowntimeAlert)
+ state_hist_bag->Set("in_host_downtime", 1);
+ }
+ else if (state_type == "STOPPED" || state_type == "CANCELLED") {
+ state_hist_bag->Set("in_downtime", 0);
+ if (log_type == LogEntryTypeHostDowntimeAlert)
+ state_hist_bag->Set("in_host_downtime", 0);
+ }
+ break;
+ default:
+ //nothing to update
+ break;
+ }
+
+ }
+
+ m_CheckablesCache[checkable] = state_hist_service_states;
+
+ /* TODO find a way to directly call addRowFn() - right now m_ServicesCache depends on historical lines ("already seen service") */
+}
+
+void StateHistTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "time", Column(&StateHistTable::TimeAccessor, objectAccessor));
+ table->AddColumn(prefix + "lineno", Column(&StateHistTable::LinenoAccessor, objectAccessor));
+ table->AddColumn(prefix + "from", Column(&StateHistTable::FromAccessor, objectAccessor));
+ table->AddColumn(prefix + "until", Column(&StateHistTable::UntilAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration", Column(&StateHistTable::DurationAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_part", Column(&StateHistTable::DurationPartAccessor, objectAccessor));
+ table->AddColumn(prefix + "state", Column(&StateHistTable::StateAccessor, objectAccessor));
+ table->AddColumn(prefix + "host_down", Column(&StateHistTable::HostDownAccessor, objectAccessor));
+ table->AddColumn(prefix + "in_downtime", Column(&StateHistTable::InDowntimeAccessor, objectAccessor));
+ table->AddColumn(prefix + "in_host_downtime", Column(&StateHistTable::InHostDowntimeAccessor, objectAccessor));
+ table->AddColumn(prefix + "is_flapping", Column(&StateHistTable::IsFlappingAccessor, objectAccessor));
+ table->AddColumn(prefix + "in_notification_period", Column(&StateHistTable::InNotificationPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "notification_period", Column(&StateHistTable::NotificationPeriodAccessor, objectAccessor));
+ table->AddColumn(prefix + "debug_info", Column(&Table::EmptyStringAccessor, objectAccessor));
+ table->AddColumn(prefix + "host_name", Column(&StateHistTable::HostNameAccessor, objectAccessor));
+ table->AddColumn(prefix + "service_description", Column(&StateHistTable::ServiceDescriptionAccessor, objectAccessor));
+ table->AddColumn(prefix + "log_output", Column(&StateHistTable::LogOutputAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_ok", Column(&StateHistTable::DurationOkAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_part_ok", Column(&StateHistTable::DurationPartOkAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_warning", Column(&StateHistTable::DurationWarningAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_part_warning", Column(&StateHistTable::DurationPartWarningAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_critical", Column(&StateHistTable::DurationCriticalAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_part_critical", Column(&StateHistTable::DurationPartCriticalAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_unknown", Column(&StateHistTable::DurationUnknownAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_part_unknown", Column(&StateHistTable::DurationPartUnknownAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_unmonitored", Column(&StateHistTable::DurationUnmonitoredAccessor, objectAccessor));
+ table->AddColumn(prefix + "duration_part_unmonitored", Column(&StateHistTable::DurationPartUnmonitoredAccessor, objectAccessor));
+
+ HostsTable::AddColumns(table, "current_host_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return HostAccessor(row, objectAccessor);
+ });
+ ServicesTable::AddColumns(table, "current_service_", [objectAccessor](const Value& row, LivestatusGroupByType, const Object::Ptr&) -> Value {
+ return ServiceAccessor(row, objectAccessor);
+ });
+}
+
+String StateHistTable::GetName() const
+{
+ return "log";
+}
+
+String StateHistTable::GetPrefix() const
+{
+ return "log";
+}
+
+void StateHistTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ Log(LogDebug, "StateHistTable")
+ << "Pre-selecting log file from " << m_TimeFrom << " until " << m_TimeUntil;
+
+ /* create log file index */
+ LivestatusLogUtility::CreateLogIndex(m_CompatLogPath, m_LogFileIndex);
+
+ /* generate log cache */
+ LivestatusLogUtility::CreateLogCache(m_LogFileIndex, this, m_TimeFrom, m_TimeUntil, addRowFn);
+
+ Checkable::Ptr checkable;
+
+ for (const auto& kv : m_CheckablesCache) {
+ for (const Dictionary::Ptr& state_hist_bag : kv.second) {
+ /* pass a dictionary from state history array */
+ if (!addRowFn(state_hist_bag, LivestatusGroupByNone, Empty))
+ return;
+ }
+ }
+}
+
+Object::Ptr StateHistTable::HostAccessor(const Value& row, const Column::ObjectAccessor&)
+{
+ String host_name = static_cast<Dictionary::Ptr>(row)->Get("host_name");
+
+ if (host_name.IsEmpty())
+ return nullptr;
+
+ return Host::GetByName(host_name);
+}
+
+Object::Ptr StateHistTable::ServiceAccessor(const Value& row, const Column::ObjectAccessor&)
+{
+ String host_name = static_cast<Dictionary::Ptr>(row)->Get("host_name");
+ String service_description = static_cast<Dictionary::Ptr>(row)->Get("service_description");
+
+ if (service_description.IsEmpty() || host_name.IsEmpty())
+ return nullptr;
+
+ return Service::GetByNamePair(host_name, service_description);
+}
+
+Value StateHistTable::TimeAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("time");
+}
+
+Value StateHistTable::LinenoAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("lineno");
+}
+
+Value StateHistTable::FromAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("from");
+}
+
+Value StateHistTable::UntilAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("until");
+}
+
+Value StateHistTable::DurationAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from"));
+}
+
+Value StateHistTable::DurationPartAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from")) / state_hist_bag->Get("query_part");
+}
+
+Value StateHistTable::StateAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("state");
+}
+
+Value StateHistTable::HostDownAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("host_down");
+}
+
+Value StateHistTable::InDowntimeAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("in_downtime");
+}
+
+Value StateHistTable::InHostDowntimeAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("in_host_downtime");
+}
+
+Value StateHistTable::IsFlappingAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("is_flapping");
+}
+
+Value StateHistTable::InNotificationPeriodAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("in_notification_period");
+}
+
+Value StateHistTable::NotificationPeriodAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("notification_period");
+}
+
+Value StateHistTable::HostNameAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("host_name");
+}
+
+Value StateHistTable::ServiceDescriptionAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("service_description");
+}
+
+Value StateHistTable::LogOutputAccessor(const Value& row)
+{
+ return static_cast<Dictionary::Ptr>(row)->Get("log_output");
+}
+
+Value StateHistTable::DurationOkAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ if (state_hist_bag->Get("state") == ServiceOK)
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from"));
+
+ return 0;
+}
+
+Value StateHistTable::DurationPartOkAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ if (state_hist_bag->Get("state") == ServiceOK)
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from")) / state_hist_bag->Get("query_part");
+
+ return 0;
+}
+
+Value StateHistTable::DurationWarningAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ if (state_hist_bag->Get("state") == ServiceWarning)
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from"));
+
+ return 0;
+}
+
+Value StateHistTable::DurationPartWarningAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ if (state_hist_bag->Get("state") == ServiceWarning)
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from")) / state_hist_bag->Get("query_part");
+
+ return 0;
+}
+
+Value StateHistTable::DurationCriticalAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ if (state_hist_bag->Get("state") == ServiceCritical)
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from"));
+
+ return 0;
+}
+
+Value StateHistTable::DurationPartCriticalAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ if (state_hist_bag->Get("state") == ServiceCritical)
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from")) / state_hist_bag->Get("query_part");
+
+ return 0;
+}
+
+Value StateHistTable::DurationUnknownAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ if (state_hist_bag->Get("state") == ServiceUnknown)
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from"));
+
+ return 0;
+}
+
+Value StateHistTable::DurationPartUnknownAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ if (state_hist_bag->Get("state") == ServiceUnknown)
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from")) / state_hist_bag->Get("query_part");
+
+ return 0;
+}
+
+Value StateHistTable::DurationUnmonitoredAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ if (state_hist_bag->Get("state") == -1)
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from"));
+
+ return 0;
+}
+
+Value StateHistTable::DurationPartUnmonitoredAccessor(const Value& row)
+{
+ Dictionary::Ptr state_hist_bag = static_cast<Dictionary::Ptr>(row);
+
+ if (state_hist_bag->Get("state") == -1)
+ return (state_hist_bag->Get("until") - state_hist_bag->Get("from")) / state_hist_bag->Get("query_part");
+
+ return 0;
+}
diff --git a/lib/livestatus/statehisttable.hpp b/lib/livestatus/statehisttable.hpp
new file mode 100644
index 0000000..db00615
--- /dev/null
+++ b/lib/livestatus/statehisttable.hpp
@@ -0,0 +1,75 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef STATEHISTTABLE_H
+#define STATEHISTTABLE_H
+
+#include "icinga/service.hpp"
+#include "livestatus/historytable.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class StateHistTable final : public HistoryTable
+{
+public:
+ DECLARE_PTR_TYPEDEFS(StateHistTable);
+
+ StateHistTable(const String& compat_log_path, time_t from, time_t until);
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+ void UpdateLogEntries(const Dictionary::Ptr& log_entry_attrs, int line_count, int lineno, const AddRowFunction& addRowFn) override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Object::Ptr HostAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+ static Object::Ptr ServiceAccessor(const Value& row, const Column::ObjectAccessor& parentObjectAccessor);
+
+ static Value TimeAccessor(const Value& row);
+ static Value LinenoAccessor(const Value& row);
+ static Value FromAccessor(const Value& row);
+ static Value UntilAccessor(const Value& row);
+ static Value DurationAccessor(const Value& row);
+ static Value DurationPartAccessor(const Value& row);
+ static Value StateAccessor(const Value& row);
+ static Value HostDownAccessor(const Value& row);
+ static Value InDowntimeAccessor(const Value& row);
+ static Value InHostDowntimeAccessor(const Value& row);
+ static Value IsFlappingAccessor(const Value& row);
+ static Value InNotificationPeriodAccessor(const Value& row);
+ static Value NotificationPeriodAccessor(const Value& row);
+ static Value HostNameAccessor(const Value& row);
+ static Value ServiceDescriptionAccessor(const Value& row);
+ static Value LogOutputAccessor(const Value& row);
+ static Value DurationOkAccessor(const Value& row);
+ static Value DurationPartOkAccessor(const Value& row);
+ static Value DurationWarningAccessor(const Value& row);
+ static Value DurationPartWarningAccessor(const Value& row);
+ static Value DurationCriticalAccessor(const Value& row);
+ static Value DurationPartCriticalAccessor(const Value& row);
+ static Value DurationUnknownAccessor(const Value& row);
+ static Value DurationPartUnknownAccessor(const Value& row);
+ static Value DurationUnmonitoredAccessor(const Value& row);
+ static Value DurationPartUnmonitoredAccessor(const Value& row);
+
+private:
+ std::map<time_t, String> m_LogFileIndex;
+ std::map<Checkable::Ptr, Array::Ptr> m_CheckablesCache;
+ time_t m_TimeFrom;
+ time_t m_TimeUntil;
+ String m_CompatLogPath;
+};
+
+}
+
+#endif /* STATEHISTTABLE_H */
diff --git a/lib/livestatus/statustable.cpp b/lib/livestatus/statustable.cpp
new file mode 100644
index 0000000..ae0a736
--- /dev/null
+++ b/lib/livestatus/statustable.cpp
@@ -0,0 +1,269 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/statustable.hpp"
+#include "livestatus/livestatuslistener.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/cib.hpp"
+#include "icinga/host.hpp"
+#include "icinga/service.hpp"
+#include "base/configtype.hpp"
+#include "base/utility.hpp"
+#include "base/application.hpp"
+
+using namespace icinga;
+
+StatusTable::StatusTable()
+{
+ AddColumns(this);
+}
+
+void StatusTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "neb_callbacks", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "neb_callbacks_rate", Column(&Table::ZeroAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "requests", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "requests_rate", Column(&Table::ZeroAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "connections", Column(&StatusTable::ConnectionsAccessor, objectAccessor));
+ table->AddColumn(prefix + "connections_rate", Column(&StatusTable::ConnectionsRateAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "service_checks", Column(&StatusTable::ServiceChecksAccessor, objectAccessor));
+ table->AddColumn(prefix + "service_checks_rate", Column(&StatusTable::ServiceChecksRateAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "host_checks", Column(&StatusTable::HostChecksAccessor, objectAccessor));
+ table->AddColumn(prefix + "host_checks_rate", Column(&StatusTable::HostChecksRateAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "forks", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "forks_rate", Column(&Table::ZeroAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "log_messages", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "log_messages_rate", Column(&Table::ZeroAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "external_commands", Column(&StatusTable::ExternalCommandsAccessor, objectAccessor));
+ table->AddColumn(prefix + "external_commands_rate", Column(&StatusTable::ExternalCommandsRateAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "livechecks", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "livechecks_rate", Column(&Table::ZeroAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "livecheck_overflows", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "livecheck_overflows_rate", Column(&Table::ZeroAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "nagios_pid", Column(&StatusTable::NagiosPidAccessor, objectAccessor));
+ table->AddColumn(prefix + "enable_notifications", Column(&StatusTable::EnableNotificationsAccessor, objectAccessor));
+ table->AddColumn(prefix + "execute_service_checks", Column(&StatusTable::ExecuteServiceChecksAccessor, objectAccessor));
+ table->AddColumn(prefix + "accept_passive_service_checks", Column(&Table::OneAccessor, objectAccessor));
+ table->AddColumn(prefix + "execute_host_checks", Column(&StatusTable::ExecuteHostChecksAccessor, objectAccessor));
+ table->AddColumn(prefix + "accept_passive_host_checks", Column(&Table::OneAccessor, objectAccessor));
+ table->AddColumn(prefix + "enable_event_handlers", Column(&StatusTable::EnableEventHandlersAccessor, objectAccessor));
+ table->AddColumn(prefix + "obsess_over_services", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "obsess_over_hosts", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_service_freshness", Column(&Table::OneAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_host_freshness", Column(&Table::OneAccessor, objectAccessor));
+ table->AddColumn(prefix + "enable_flap_detection", Column(&StatusTable::EnableFlapDetectionAccessor, objectAccessor));
+ table->AddColumn(prefix + "process_performance_data", Column(&StatusTable::ProcessPerformanceDataAccessor, objectAccessor));
+ table->AddColumn(prefix + "check_external_commands", Column(&Table::OneAccessor, objectAccessor));
+ table->AddColumn(prefix + "program_start", Column(&StatusTable::ProgramStartAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_command_check", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "last_log_rotation", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "interval_length", Column(&StatusTable::IntervalLengthAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_hosts", Column(&StatusTable::NumHostsAccessor, objectAccessor));
+ table->AddColumn(prefix + "num_services", Column(&StatusTable::NumServicesAccessor, objectAccessor));
+ table->AddColumn(prefix + "program_version", Column(&StatusTable::ProgramVersionAccessor, objectAccessor));
+ table->AddColumn(prefix + "external_command_buffer_slots", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "external_command_buffer_usage", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "external_command_buffer_max", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "cached_log_messages", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "livestatus_version", Column(&StatusTable::LivestatusVersionAccessor, objectAccessor));
+ table->AddColumn(prefix + "livestatus_active_connections", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "livestatus_queued_connections", Column(&Table::ZeroAccessor, objectAccessor));
+ table->AddColumn(prefix + "livestatus_threads", Column(&Table::ZeroAccessor, objectAccessor));
+
+ table->AddColumn(prefix + "custom_variable_names", Column(&StatusTable::CustomVariableNamesAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variable_values", Column(&StatusTable::CustomVariableValuesAccessor, objectAccessor));
+ table->AddColumn(prefix + "custom_variables", Column(&StatusTable::CustomVariablesAccessor, objectAccessor));
+}
+
+String StatusTable::GetName() const
+{
+ return "status";
+}
+
+String StatusTable::GetPrefix() const
+{
+ return "status";
+}
+
+void StatusTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ Object::Ptr obj = new Object();
+
+ /* Return a fake row. */
+ addRowFn(obj, LivestatusGroupByNone, Empty);
+}
+
+Value StatusTable::ConnectionsAccessor(const Value&)
+{
+ return LivestatusListener::GetConnections();
+}
+
+Value StatusTable::ConnectionsRateAccessor(const Value&)
+{
+ return (LivestatusListener::GetConnections() / (Utility::GetTime() - Application::GetStartTime()));
+}
+
+Value StatusTable::HostChecksAccessor(const Value&)
+{
+ auto timespan = static_cast<long>(Utility::GetTime() - Application::GetStartTime());
+ return CIB::GetActiveHostChecksStatistics(timespan);
+}
+
+Value StatusTable::HostChecksRateAccessor(const Value&)
+{
+ auto timespan = static_cast<long>(Utility::GetTime() - Application::GetStartTime());
+ return (CIB::GetActiveHostChecksStatistics(timespan) / (Utility::GetTime() - Application::GetStartTime()));
+}
+
+Value StatusTable::ServiceChecksAccessor(const Value&)
+{
+ auto timespan = static_cast<long>(Utility::GetTime() - Application::GetStartTime());
+ return CIB::GetActiveServiceChecksStatistics(timespan);
+}
+
+Value StatusTable::ServiceChecksRateAccessor(const Value&)
+{
+ auto timespan = static_cast<long>(Utility::GetTime() - Application::GetStartTime());
+ return (CIB::GetActiveServiceChecksStatistics(timespan) / (Utility::GetTime() - Application::GetStartTime()));
+}
+
+Value StatusTable::ExternalCommandsAccessor(const Value&)
+{
+ return LivestatusQuery::GetExternalCommands();
+}
+
+Value StatusTable::ExternalCommandsRateAccessor(const Value&)
+{
+ return (LivestatusQuery::GetExternalCommands() / (Utility::GetTime() - Application::GetStartTime()));
+}
+
+Value StatusTable::NagiosPidAccessor(const Value&)
+{
+ return Utility::GetPid();
+}
+
+Value StatusTable::EnableNotificationsAccessor(const Value&)
+{
+ return (IcingaApplication::GetInstance()->GetEnableNotifications() ? 1 : 0);
+}
+
+Value StatusTable::ExecuteServiceChecksAccessor(const Value&)
+{
+ return (IcingaApplication::GetInstance()->GetEnableServiceChecks() ? 1 : 0);
+}
+
+Value StatusTable::ExecuteHostChecksAccessor(const Value&)
+{
+ return (IcingaApplication::GetInstance()->GetEnableHostChecks() ? 1 : 0);
+}
+
+Value StatusTable::EnableEventHandlersAccessor(const Value&)
+{
+ return (IcingaApplication::GetInstance()->GetEnableEventHandlers() ? 1 : 0);
+}
+
+Value StatusTable::EnableFlapDetectionAccessor(const Value&)
+{
+ return (IcingaApplication::GetInstance()->GetEnableFlapping() ? 1 : 0);
+}
+
+Value StatusTable::ProcessPerformanceDataAccessor(const Value&)
+{
+ return (IcingaApplication::GetInstance()->GetEnablePerfdata() ? 1 : 0);
+}
+
+Value StatusTable::ProgramStartAccessor(const Value&)
+{
+ return static_cast<long>(Application::GetStartTime());
+}
+
+Value StatusTable::IntervalLengthAccessor(const Value&)
+{
+ return LIVESTATUS_INTERVAL_LENGTH;
+}
+
+Value StatusTable::NumHostsAccessor(const Value&)
+{
+ return ConfigType::Get<Host>()->GetObjectCount();
+}
+
+Value StatusTable::NumServicesAccessor(const Value&)
+{
+ return ConfigType::Get<Service>()->GetObjectCount();
+}
+
+Value StatusTable::ProgramVersionAccessor(const Value&)
+{
+ return Application::GetAppVersion() + "-icinga2";
+}
+
+Value StatusTable::LivestatusVersionAccessor(const Value&)
+{
+ return Application::GetAppVersion();
+}
+
+Value StatusTable::LivestatusActiveConnectionsAccessor(const Value&)
+{
+ return LivestatusListener::GetClientsConnected();
+}
+
+Value StatusTable::CustomVariableNamesAccessor(const Value&)
+{
+ Dictionary::Ptr vars = IcingaApplication::GetInstance()->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const auto& kv : vars) {
+ result.push_back(kv.first);
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value StatusTable::CustomVariableValuesAccessor(const Value&)
+{
+ Dictionary::Ptr vars = IcingaApplication::GetInstance()->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const auto& kv : vars) {
+ result.push_back(kv.second);
+ }
+ }
+
+ return new Array(std::move(result));
+}
+
+Value StatusTable::CustomVariablesAccessor(const Value&)
+{
+ Dictionary::Ptr vars = IcingaApplication::GetInstance()->GetVars();
+
+ ArrayData result;
+
+ if (vars) {
+ ObjectLock olock(vars);
+ for (const auto& kv : vars) {
+ result.push_back(new Array({
+ kv.first,
+ kv.second
+ }));
+ }
+ }
+
+ return new Array(std::move(result));
+}
diff --git a/lib/livestatus/statustable.hpp b/lib/livestatus/statustable.hpp
new file mode 100644
index 0000000..2fba249
--- /dev/null
+++ b/lib/livestatus/statustable.hpp
@@ -0,0 +1,61 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef STATUSTABLE_H
+#define STATUSTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class StatusTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(StatusTable);
+
+ StatusTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Value ConnectionsAccessor(const Value& row);
+ static Value ConnectionsRateAccessor(const Value& row);
+ static Value ServiceChecksAccessor(const Value& row);
+ static Value ServiceChecksRateAccessor(const Value& row);
+ static Value HostChecksAccessor(const Value& row);
+ static Value HostChecksRateAccessor(const Value& row);
+ static Value ExternalCommandsAccessor(const Value& row);
+ static Value ExternalCommandsRateAccessor(const Value& row);
+ static Value NagiosPidAccessor(const Value& row);
+ static Value EnableNotificationsAccessor(const Value& row);
+ static Value ExecuteServiceChecksAccessor(const Value& row);
+ static Value ExecuteHostChecksAccessor(const Value& row);
+ static Value EnableEventHandlersAccessor(const Value& row);
+ static Value EnableFlapDetectionAccessor(const Value& row);
+ static Value ProcessPerformanceDataAccessor(const Value& row);
+ static Value ProgramStartAccessor(const Value& row);
+ static Value IntervalLengthAccessor(const Value& row);
+ static Value NumHostsAccessor(const Value& row);
+ static Value NumServicesAccessor(const Value& row);
+ static Value ProgramVersionAccessor(const Value& row);
+ static Value LivestatusVersionAccessor(const Value& row);
+ static Value LivestatusActiveConnectionsAccessor(const Value& row);
+ static Value CustomVariableNamesAccessor(const Value& row);
+ static Value CustomVariableValuesAccessor(const Value& row);
+ static Value CustomVariablesAccessor(const Value& row);
+};
+
+}
+
+#endif /* STATUSTABLE_H */
diff --git a/lib/livestatus/stdaggregator.cpp b/lib/livestatus/stdaggregator.cpp
new file mode 100644
index 0000000..99c3a8e
--- /dev/null
+++ b/lib/livestatus/stdaggregator.cpp
@@ -0,0 +1,40 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/stdaggregator.hpp"
+#include <math.h>
+
+using namespace icinga;
+
+StdAggregator::StdAggregator(String attr)
+ : m_StdAttr(std::move(attr))
+{ }
+
+StdAggregatorState *StdAggregator::EnsureState(AggregatorState **state)
+{
+ if (!*state)
+ *state = new StdAggregatorState();
+
+ return static_cast<StdAggregatorState *>(*state);
+}
+
+void StdAggregator::Apply(const Table::Ptr& table, const Value& row, AggregatorState **state)
+{
+ Column column = table->GetColumn(m_StdAttr);
+
+ Value value = column.ExtractValue(row);
+
+ StdAggregatorState *pstate = EnsureState(state);
+
+ pstate->StdSum += value;
+ pstate->StdQSum += pow(value, 2);
+ pstate->StdCount++;
+}
+
+double StdAggregator::GetResultAndFreeState(AggregatorState *state) const
+{
+ StdAggregatorState *pstate = EnsureState(&state);
+ double result = sqrt((pstate->StdQSum - (1 / pstate->StdCount) * pow(pstate->StdSum, 2)) / (pstate->StdCount - 1));
+ delete pstate;
+
+ return result;
+}
diff --git a/lib/livestatus/stdaggregator.hpp b/lib/livestatus/stdaggregator.hpp
new file mode 100644
index 0000000..3680fe7
--- /dev/null
+++ b/lib/livestatus/stdaggregator.hpp
@@ -0,0 +1,43 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef STDAGGREGATOR_H
+#define STDAGGREGATOR_H
+
+#include "livestatus/table.hpp"
+#include "livestatus/aggregator.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+struct StdAggregatorState final : public AggregatorState
+{
+ double StdSum{0};
+ double StdQSum{0};
+ double StdCount{0};
+};
+
+/**
+ * @ingroup livestatus
+ */
+class StdAggregator final : public Aggregator
+{
+public:
+ DECLARE_PTR_TYPEDEFS(StdAggregator);
+
+ StdAggregator(String attr);
+
+ void Apply(const Table::Ptr& table, const Value& row, AggregatorState **state) override;
+ double GetResultAndFreeState(AggregatorState *state) const override;
+
+private:
+ String m_StdAttr;
+
+ static StdAggregatorState *EnsureState(AggregatorState **state);
+};
+
+}
+
+#endif /* STDAGGREGATOR_H */
diff --git a/lib/livestatus/sumaggregator.cpp b/lib/livestatus/sumaggregator.cpp
new file mode 100644
index 0000000..fc4b62e
--- /dev/null
+++ b/lib/livestatus/sumaggregator.cpp
@@ -0,0 +1,37 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/sumaggregator.hpp"
+
+using namespace icinga;
+
+SumAggregator::SumAggregator(String attr)
+ : m_SumAttr(std::move(attr))
+{ }
+
+SumAggregatorState *SumAggregator::EnsureState(AggregatorState **state)
+{
+ if (!*state)
+ *state = new SumAggregatorState();
+
+ return static_cast<SumAggregatorState *>(*state);
+}
+
+void SumAggregator::Apply(const Table::Ptr& table, const Value& row, AggregatorState **state)
+{
+ Column column = table->GetColumn(m_SumAttr);
+
+ Value value = column.ExtractValue(row);
+
+ SumAggregatorState *pstate = EnsureState(state);
+
+ pstate->Sum += value;
+}
+
+double SumAggregator::GetResultAndFreeState(AggregatorState *state) const
+{
+ SumAggregatorState *pstate = EnsureState(&state);
+ double result = pstate->Sum;
+ delete pstate;
+
+ return result;
+}
diff --git a/lib/livestatus/sumaggregator.hpp b/lib/livestatus/sumaggregator.hpp
new file mode 100644
index 0000000..23f22fb
--- /dev/null
+++ b/lib/livestatus/sumaggregator.hpp
@@ -0,0 +1,41 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef SUMAGGREGATOR_H
+#define SUMAGGREGATOR_H
+
+#include "livestatus/table.hpp"
+#include "livestatus/aggregator.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+struct SumAggregatorState final : public AggregatorState
+{
+ double Sum{0};
+};
+
+/**
+ * @ingroup livestatus
+ */
+class SumAggregator final : public Aggregator
+{
+public:
+ DECLARE_PTR_TYPEDEFS(SumAggregator);
+
+ SumAggregator(String attr);
+
+ void Apply(const Table::Ptr& table, const Value& row, AggregatorState **state) override;
+ double GetResultAndFreeState(AggregatorState *state) const override;
+
+private:
+ String m_SumAttr;
+
+ static SumAggregatorState *EnsureState(AggregatorState **state);
+};
+
+}
+
+#endif /* SUMAGGREGATOR_H */
diff --git a/lib/livestatus/table.cpp b/lib/livestatus/table.cpp
new file mode 100644
index 0000000..8e5d85a
--- /dev/null
+++ b/lib/livestatus/table.cpp
@@ -0,0 +1,165 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/table.hpp"
+#include "livestatus/statustable.hpp"
+#include "livestatus/contactgroupstable.hpp"
+#include "livestatus/contactstable.hpp"
+#include "livestatus/hostgroupstable.hpp"
+#include "livestatus/hoststable.hpp"
+#include "livestatus/servicegroupstable.hpp"
+#include "livestatus/servicestable.hpp"
+#include "livestatus/commandstable.hpp"
+#include "livestatus/commentstable.hpp"
+#include "livestatus/downtimestable.hpp"
+#include "livestatus/endpointstable.hpp"
+#include "livestatus/zonestable.hpp"
+#include "livestatus/timeperiodstable.hpp"
+#include "livestatus/logtable.hpp"
+#include "livestatus/statehisttable.hpp"
+#include "livestatus/filter.hpp"
+#include "base/array.hpp"
+#include "base/dictionary.hpp"
+#include <boost/algorithm/string/case_conv.hpp>
+
+using namespace icinga;
+
+Table::Table(LivestatusGroupByType type)
+ : m_GroupByType(type), m_GroupByObject(Empty)
+{ }
+
+Table::Ptr Table::GetByName(const String& name, const String& compat_log_path, const unsigned long& from, const unsigned long& until)
+{
+ if (name == "status")
+ return new StatusTable();
+ else if (name == "contactgroups")
+ return new ContactGroupsTable();
+ else if (name == "contacts")
+ return new ContactsTable();
+ else if (name == "hostgroups")
+ return new HostGroupsTable();
+ else if (name == "hosts")
+ return new HostsTable();
+ else if (name == "hostsbygroup")
+ return new HostsTable(LivestatusGroupByHostGroup);
+ else if (name == "servicegroups")
+ return new ServiceGroupsTable();
+ else if (name == "services")
+ return new ServicesTable();
+ else if (name == "servicesbygroup")
+ return new ServicesTable(LivestatusGroupByServiceGroup);
+ else if (name == "servicesbyhostgroup")
+ return new ServicesTable(LivestatusGroupByHostGroup);
+ else if (name == "commands")
+ return new CommandsTable();
+ else if (name == "comments")
+ return new CommentsTable();
+ else if (name == "downtimes")
+ return new DowntimesTable();
+ else if (name == "timeperiods")
+ return new TimePeriodsTable();
+ else if (name == "log")
+ return new LogTable(compat_log_path, from, until);
+ else if (name == "statehist")
+ return new StateHistTable(compat_log_path, from, until);
+ else if (name == "endpoints")
+ return new EndpointsTable();
+ else if (name == "zones")
+ return new ZonesTable();
+
+ return nullptr;
+}
+
+void Table::AddColumn(const String& name, const Column& column)
+{
+ std::pair<String, Column> item = std::make_pair(name, column);
+
+ auto ret = m_Columns.insert(item);
+
+ if (!ret.second)
+ ret.first->second = column;
+}
+
+Column Table::GetColumn(const String& name) const
+{
+ String dname = name;
+ String prefix = GetPrefix() + "_";
+
+ if (dname.Find(prefix) == 0)
+ dname = dname.SubStr(prefix.GetLength());
+
+ auto it = m_Columns.find(dname);
+
+ if (it == m_Columns.end())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Column '" + dname + "' does not exist in table '" + GetName() + "'."));
+
+ return it->second;
+}
+
+std::vector<String> Table::GetColumnNames() const
+{
+ std::vector<String> names;
+
+ for (const auto& kv : m_Columns) {
+ names.push_back(kv.first);
+ }
+
+ return names;
+}
+
+std::vector<LivestatusRowValue> Table::FilterRows(const Filter::Ptr& filter, int limit)
+{
+ std::vector<LivestatusRowValue> rs;
+
+ FetchRows([this, filter, limit, &rs](const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject) {
+ return FilteredAddRow(rs, filter, limit, row, groupByType, groupByObject);
+ });
+
+ return rs;
+}
+
+bool Table::FilteredAddRow(std::vector<LivestatusRowValue>& rs, const Filter::Ptr& filter, int limit, const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject)
+{
+ if (limit != -1 && static_cast<int>(rs.size()) == limit)
+ return false;
+
+ if (!filter || filter->Apply(this, row)) {
+ LivestatusRowValue rval;
+ rval.Row = row;
+ rval.GroupByType = groupByType;
+ rval.GroupByObject = groupByObject;
+
+ rs.emplace_back(std::move(rval));
+ }
+
+ return true;
+}
+
+Value Table::ZeroAccessor(const Value&)
+{
+ return 0;
+}
+
+Value Table::OneAccessor(const Value&)
+{
+ return 1;
+}
+
+Value Table::EmptyStringAccessor(const Value&)
+{
+ return "";
+}
+
+Value Table::EmptyArrayAccessor(const Value&)
+{
+ return new Array();
+}
+
+Value Table::EmptyDictionaryAccessor(const Value&)
+{
+ return new Dictionary();
+}
+
+LivestatusGroupByType Table::GetGroupByType() const
+{
+ return m_GroupByType;
+}
diff --git a/lib/livestatus/table.hpp b/lib/livestatus/table.hpp
new file mode 100644
index 0000000..fa3fc2a
--- /dev/null
+++ b/lib/livestatus/table.hpp
@@ -0,0 +1,73 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef TABLE_H
+#define TABLE_H
+
+#include "livestatus/column.hpp"
+#include "base/object.hpp"
+#include "base/dictionary.hpp"
+#include "base/array.hpp"
+#include <vector>
+
+namespace icinga
+{
+
+// Well, don't ask.
+#define LIVESTATUS_INTERVAL_LENGTH 60.0
+
+struct LivestatusRowValue {
+ Value Row;
+ LivestatusGroupByType GroupByType;
+ Value GroupByObject;
+};
+
+typedef std::function<bool (const Value&, LivestatusGroupByType, const Object::Ptr&)> AddRowFunction;
+
+class Filter;
+
+/**
+ * @ingroup livestatus
+ */
+class Table : public Object
+{
+public:
+ DECLARE_PTR_TYPEDEFS(Table);
+
+ static Table::Ptr GetByName(const String& name, const String& compat_log_path = "", const unsigned long& from = 0, const unsigned long& until = 0);
+
+ virtual String GetName() const = 0;
+ virtual String GetPrefix() const = 0;
+
+ std::vector<LivestatusRowValue> FilterRows(const intrusive_ptr<Filter>& filter, int limit = -1);
+
+ void AddColumn(const String& name, const Column& column);
+ Column GetColumn(const String& name) const;
+ std::vector<String> GetColumnNames() const;
+
+ LivestatusGroupByType GetGroupByType() const;
+
+protected:
+ Table(LivestatusGroupByType type = LivestatusGroupByNone);
+
+ virtual void FetchRows(const AddRowFunction& addRowFn) = 0;
+
+ static Value ZeroAccessor(const Value&);
+ static Value OneAccessor(const Value&);
+ static Value EmptyStringAccessor(const Value&);
+ static Value EmptyArrayAccessor(const Value&);
+ static Value EmptyDictionaryAccessor(const Value&);
+
+ LivestatusGroupByType m_GroupByType;
+ Value m_GroupByObject;
+
+private:
+ std::map<String, Column> m_Columns;
+
+ bool FilteredAddRow(std::vector<LivestatusRowValue>& rs, const intrusive_ptr<Filter>& filter, int limit, const Value& row, LivestatusGroupByType groupByType, const Object::Ptr& groupByObject);
+};
+
+}
+
+#endif /* TABLE_H */
+
+#include "livestatus/filter.hpp"
diff --git a/lib/livestatus/timeperiodstable.cpp b/lib/livestatus/timeperiodstable.cpp
new file mode 100644
index 0000000..5797d93
--- /dev/null
+++ b/lib/livestatus/timeperiodstable.cpp
@@ -0,0 +1,58 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/timeperiodstable.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/timeperiod.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/convert.hpp"
+#include "base/utility.hpp"
+#include <boost/algorithm/string/replace.hpp>
+
+using namespace icinga;
+
+TimePeriodsTable::TimePeriodsTable()
+{
+ AddColumns(this);
+}
+
+void TimePeriodsTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "name", Column(&TimePeriodsTable::NameAccessor, objectAccessor));
+ table->AddColumn(prefix + "alias", Column(&TimePeriodsTable::AliasAccessor, objectAccessor));
+ table->AddColumn(prefix + "in", Column(&TimePeriodsTable::InAccessor, objectAccessor));
+}
+
+String TimePeriodsTable::GetName() const
+{
+ return "timeperiod";
+}
+
+String TimePeriodsTable::GetPrefix() const
+{
+ return "timeperiod";
+}
+
+void TimePeriodsTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ for (const TimePeriod::Ptr& tp : ConfigType::GetObjectsByType<TimePeriod>()) {
+ if (!addRowFn(tp, LivestatusGroupByNone, Empty))
+ return;
+ }
+}
+
+Value TimePeriodsTable::NameAccessor(const Value& row)
+{
+ return static_cast<TimePeriod::Ptr>(row)->GetName();
+}
+
+Value TimePeriodsTable::AliasAccessor(const Value& row)
+{
+ return static_cast<TimePeriod::Ptr>(row)->GetDisplayName();
+}
+
+Value TimePeriodsTable::InAccessor(const Value& row)
+{
+ return (static_cast<TimePeriod::Ptr>(row)->IsInside(Utility::GetTime()) ? 1 : 0);
+}
diff --git a/lib/livestatus/timeperiodstable.hpp b/lib/livestatus/timeperiodstable.hpp
new file mode 100644
index 0000000..31cef93
--- /dev/null
+++ b/lib/livestatus/timeperiodstable.hpp
@@ -0,0 +1,39 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef TIMEPERIODSTABLE_H
+#define TIMEPERIODSTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class TimePeriodsTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(TimePeriodsTable);
+
+ TimePeriodsTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Value NameAccessor(const Value& row);
+ static Value AliasAccessor(const Value& row);
+ static Value InAccessor(const Value& row);
+};
+
+}
+
+#endif /* TIMEPERIODSTABLE_H */
diff --git a/lib/livestatus/zonestable.cpp b/lib/livestatus/zonestable.cpp
new file mode 100644
index 0000000..d86cc72
--- /dev/null
+++ b/lib/livestatus/zonestable.cpp
@@ -0,0 +1,92 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/zonestable.hpp"
+#include "remote/zone.hpp"
+#include "base/configtype.hpp"
+
+using namespace icinga;
+
+ZonesTable::ZonesTable()
+{
+ AddColumns(this);
+}
+
+void ZonesTable::AddColumns(Table *table, const String& prefix,
+ const Column::ObjectAccessor& objectAccessor)
+{
+ table->AddColumn(prefix + "name", Column(&ZonesTable::NameAccessor, objectAccessor));
+ table->AddColumn(prefix + "parent", Column(&ZonesTable::ParentAccessor, objectAccessor));
+ table->AddColumn(prefix + "endpoints", Column(&ZonesTable::EndpointsAccessor, objectAccessor));
+ table->AddColumn(prefix + "global", Column(&ZonesTable::GlobalAccessor, objectAccessor));
+}
+
+String ZonesTable::GetName() const
+{
+ return "zones";
+}
+
+String ZonesTable::GetPrefix() const
+{
+ return "zone";
+}
+
+void ZonesTable::FetchRows(const AddRowFunction& addRowFn)
+{
+ for (const Zone::Ptr& zone : ConfigType::GetObjectsByType<Zone>()) {
+ if (!addRowFn(zone, LivestatusGroupByNone, Empty))
+ return;
+ }
+}
+
+Value ZonesTable::NameAccessor(const Value& row)
+{
+ Zone::Ptr zone = static_cast<Zone::Ptr>(row);
+
+ if (!zone)
+ return Empty;
+
+ return zone->GetName();
+}
+
+Value ZonesTable::ParentAccessor(const Value& row)
+{
+ Zone::Ptr zone = static_cast<Zone::Ptr>(row);
+
+ if (!zone)
+ return Empty;
+
+ Zone::Ptr parent_zone = zone->GetParent();
+
+ if (!parent_zone)
+ return Empty;
+
+ return parent_zone->GetName();
+}
+
+Value ZonesTable::EndpointsAccessor(const Value& row)
+{
+ Zone::Ptr zone = static_cast<Zone::Ptr>(row);
+
+ if (!zone)
+ return Empty;
+
+ std::set<Endpoint::Ptr> endpoints = zone->GetEndpoints();
+
+ ArrayData result;
+
+ for (const Endpoint::Ptr& endpoint : endpoints) {
+ result.push_back(endpoint->GetName());
+ }
+
+ return new Array(std::move(result));
+}
+
+Value ZonesTable::GlobalAccessor(const Value& row)
+{
+ Zone::Ptr zone = static_cast<Zone::Ptr>(row);
+
+ if (!zone)
+ return Empty;
+
+ return zone->GetGlobal() ? 1 : 0;
+}
diff --git a/lib/livestatus/zonestable.hpp b/lib/livestatus/zonestable.hpp
new file mode 100644
index 0000000..fb03488
--- /dev/null
+++ b/lib/livestatus/zonestable.hpp
@@ -0,0 +1,40 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef ZONESTABLE_H
+#define ZONESTABLE_H
+
+#include "livestatus/table.hpp"
+
+using namespace icinga;
+
+namespace icinga
+{
+
+/**
+ * @ingroup livestatus
+ */
+class ZonesTable final : public Table
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ZonesTable);
+
+ ZonesTable();
+
+ static void AddColumns(Table *table, const String& prefix = String(),
+ const Column::ObjectAccessor& objectAccessor = Column::ObjectAccessor());
+
+ String GetName() const override;
+ String GetPrefix() const override;
+
+protected:
+ void FetchRows(const AddRowFunction& addRowFn) override;
+
+ static Value NameAccessor(const Value& row);
+ static Value ParentAccessor(const Value& row);
+ static Value EndpointsAccessor(const Value& row);
+ static Value GlobalAccessor(const Value& row);
+};
+
+}
+
+#endif /* ZONESTABLE_H */