diff --git a/src/database/single_node/graph_db.hpp b/src/database/single_node/graph_db.hpp index 110ffe2ed..6dc80884e 100644 --- a/src/database/single_node/graph_db.hpp +++ b/src/database/single_node/graph_db.hpp @@ -19,6 +19,23 @@ namespace database { +/// Struct containing basic statistics about storage. +struct Stat { + // std::atomic<long> is needed as reference to stat is passed to + // other threads. If there were no std::atomic we couldn't guarantee + // that a change to any member will be visible to other threads. + + /// Vertex count is number of `VersionList<Vertex>` physically stored. + std::atomic<int64_t> vertex_count{0}; + + /// Vertex count is number of `VersionList<Edge>` physically stored. + std::atomic<int64_t> edge_count{0}; + + /// Average in/out degree of a vertex. + /// `avg_degree` is calculated as 2 * `edges_count` / `vertex_count`. + std::atomic<double> avg_degree{0}; +}; + /// Database configuration. Initialized from flags, but modifiable. struct Config { Config(); @@ -101,7 +118,27 @@ class GraphDb { /// When this is false, no new transactions should be created. bool is_accepting_transactions() const { return is_accepting_transactions_; } + /// Get live view of storage stats. Gets updated on RefreshStat. + const Stat &GetStat() const { return stat_; } + + /// Updates storage stats. + void RefreshStat() { + auto vertex_count = storage().vertices_.access().size(); + auto edge_count = storage().edges_.access().size(); + + stat_.vertex_count = vertex_count; + stat_.edge_count = edge_count; + + if (vertex_count != 0) { + stat_.avg_degree = 2 * static_cast<double>(edge_count) / vertex_count; + } else { + stat_.avg_degree = 0; + } + } + protected: + Stat stat_; + std::atomic<bool> is_accepting_transactions_{true}; std::unique_ptr<utils::Scheduler> snapshot_creator_; diff --git a/src/database/single_node_ha/graph_db.hpp b/src/database/single_node_ha/graph_db.hpp index 5b2a05c0f..8f145351c 100644 --- a/src/database/single_node_ha/graph_db.hpp +++ b/src/database/single_node_ha/graph_db.hpp @@ -19,6 +19,23 @@ namespace database { +/// Struct containing basic statistics about storage. +struct Stat { + // std::atomic<int64_t> is needed as reference to stat is passed to + // other threads. If there were no std::atomic we couldn't guarantee + // that a change to any member will be visible to other threads. + + /// Vertex count is number of `VersionList<Vertex>` physically stored. + std::atomic<int64_t> vertex_count{0}; + + /// Vertex count is number of `VersionList<Edge>` physically stored. + std::atomic<int64_t> edge_count{0}; + + /// Average in/out degree of a vertex. + /// `avg_degree` is calculated as 2 * `edges_count` / `vertex_count`. + std::atomic<double> avg_degree{0}; +}; + /// Database configuration. Initialized from flags, but modifiable. struct Config { Config(); @@ -101,7 +118,27 @@ class GraphDb { /// When this is false, no new transactions should be created. bool is_accepting_transactions() const { return is_accepting_transactions_; } + /// Get live view of storage stats. Gets updated on RefreshStat. + const Stat &GetStat() const { return stat_; } + + /// Updates storage stats. + void RefreshStat() { + auto vertex_count = storage().vertices_.access().size(); + auto edge_count = storage().edges_.access().size(); + + stat_.vertex_count = vertex_count; + stat_.edge_count = edge_count; + + if (vertex_count != 0) { + stat_.avg_degree = 2 * static_cast<double>(edge_count) / vertex_count; + } else { + stat_.avg_degree = 0; + } + } + protected: + Stat stat_; + std::atomic<bool> is_accepting_transactions_{true}; std::unique_ptr<utils::Scheduler> snapshot_creator_; diff --git a/src/storage/single_node/storage.hpp b/src/storage/single_node/storage.hpp index 46d995105..e940c0001 100644 --- a/src/storage/single_node/storage.hpp +++ b/src/storage/single_node/storage.hpp @@ -60,6 +60,8 @@ class Storage { private: friend class GraphDbAccessor; + // Because of GraphDb::RefreshStat + friend class GraphDb; friend class StorageGc; gid::Generator vertex_generator_; diff --git a/src/storage/single_node_ha/storage.hpp b/src/storage/single_node_ha/storage.hpp index 6eb9e2f5d..9c13a59ac 100644 --- a/src/storage/single_node_ha/storage.hpp +++ b/src/storage/single_node_ha/storage.hpp @@ -60,6 +60,8 @@ class Storage { private: friend class GraphDbAccessor; + // Needed for GraphDb::RefreshStat. + friend class GraphDb; friend class StorageGc; gid::Generator vertex_generator_; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 76d78dc81..5acf2395b 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -234,6 +234,9 @@ target_link_libraries(${test_prefix}static_bitset mg-single-node kvstore_dummy_l add_unit_test(storage_address.cpp) target_link_libraries(${test_prefix}storage_address mg-distributed kvstore_dummy_lib) +add_unit_test(storage_stat.cpp) +target_link_libraries(${test_prefix}storage_stat mg-single-node kvstore_dummy_lib) + add_unit_test(stripped.cpp) target_link_libraries(${test_prefix}stripped mg-single-node kvstore_dummy_lib) diff --git a/tests/unit/storage_stat.cpp b/tests/unit/storage_stat.cpp new file mode 100644 index 000000000..35dcdfdc2 --- /dev/null +++ b/tests/unit/storage_stat.cpp @@ -0,0 +1,71 @@ +#include <glog/logging.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "database/single_node/graph_db.hpp" +#include "database/single_node/graph_db_accessor.hpp" + +class StatTest : public ::testing::Test { + public: + database::GraphDb db_; +}; + +#define COMPARE(stat, vc, ec, ad) \ + EXPECT_EQ(stat.vertex_count, vc); \ + EXPECT_EQ(stat.edge_count, ec); \ + EXPECT_EQ(stat.avg_degree, ad); + +TEST_F(StatTest, CountTest1) { + auto &stat = db_.GetStat(); + COMPARE(stat, 0, 0, 0); + + auto dba = db_.Access(); + dba->InsertVertex(); + dba->InsertVertex(); + dba->InsertVertex(); + + COMPARE(stat, 0, 0, 0); + db_.RefreshStat(); + COMPARE(stat, 3, 0, 0); +} + +TEST_F(StatTest, CountTest2) { + auto &stat = db_.GetStat(); + COMPARE(stat, 0, 0, 0); + + auto dba = db_.Access(); + auto type = dba->EdgeType("edge"); + auto v1 = dba->InsertVertex(); + auto v2 = dba->InsertVertex(); + auto v3 = dba->InsertVertex(); + auto v4 = dba->InsertVertex(); + auto e1 = dba->InsertEdge(v1, v2, type); + auto e2 = dba->InsertEdge(v2, v2, type); + auto e3 = dba->InsertEdge(v3, v2, type); + auto e4 = dba->InsertEdge(v4, v2, type); + auto e5 = dba->InsertEdge(v1, v3, type); + + COMPARE(stat, 0, 0, 0); + db_.RefreshStat(); + COMPARE(stat, 4, 5, 2.5); + + dba->Commit(); + + auto dba1 = db_.Access(); + auto v22 = dba1->FindVertex(v2.gid(), true); + dba1->DetachRemoveVertex(v22); + + db_.RefreshStat(); + COMPARE(stat, 4, 5, 2.5); + + dba1->Commit(); + db_.CollectGarbage(); + db_.RefreshStat(); + COMPARE(stat, 3, 1, 2.0 / 3); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + google::InitGoogleLogging(argv[0]); + return RUN_ALL_TESTS(); +}