From 3ccdae49b54ca1d6e82425cbea633a743df57098 Mon Sep 17 00:00:00 2001
From: joana <uhfpm@student.kit.edu>
Date: Thu, 9 Nov 2023 11:56:43 +0100
Subject: [PATCH] only record one LTM instance again and include date prefix
 for export-path

---
 .../armem/server/MemoryToIceAdapter.cpp       | 15 ++++----
 .../armem/server/MemoryToIceAdapter.h         |  2 +-
 .../libraries/armem/server/ltm/Memory.cpp     | 22 +++++++++++-
 .../libraries/armem/server/ltm/Memory.h       |  3 ++
 .../armem/server/ltm/detail/MemoryBase.h      |  7 ++++
 .../armem/server/ltm/detail/MemoryItem.h      |  6 ++--
 .../ltm/detail/mixins/DiskStorageMixin.cpp    | 10 +++++-
 .../ltm/detail/mixins/DiskStorageMixin.h      |  1 +
 .../libraries/armem/server/plugins/Plugin.cpp | 36 +++++++++++++------
 .../libraries/armem/server/plugins/Plugin.h   |  3 +-
 10 files changed, 82 insertions(+), 23 deletions(-)

diff --git a/source/RobotAPI/libraries/armem/server/MemoryToIceAdapter.cpp b/source/RobotAPI/libraries/armem/server/MemoryToIceAdapter.cpp
index d9c6a40aa..a6a2f0d8b 100644
--- a/source/RobotAPI/libraries/armem/server/MemoryToIceAdapter.cpp
+++ b/source/RobotAPI/libraries/armem/server/MemoryToIceAdapter.cpp
@@ -36,6 +36,7 @@ namespace armarx::armem::server
     void MemoryToIceAdapter::addSecondLTM(ltm::Memory *longtermMemory)
     {
         std::string name = longtermMemory->getExportName();
+        /*
         if(name.find("MemoryExport2") != std::string::npos){
             this->secondLTM = longtermMemory;
             ARMARX_INFO << "added second longterm Memory";
@@ -66,7 +67,7 @@ namespace armarx::armem::server
         } else if (name.find("MemoryExport11") != std::string::npos){
             this->eleventhLTM = longtermMemory;
             ARMARX_INFO << "added LTM 11";
-        } /*else if (name.find("MemoryExport12") != std::string::npos){
+        } else if (name.find("MemoryExport12") != std::string::npos){
             this->twelvethLTM = longtermMemory;
             ARMARX_INFO << "added LTM 12";
         } else if (name.find("MemoryExport13") != std::string::npos){
@@ -81,12 +82,13 @@ namespace armarx::armem::server
         } else if (name.find("MemoryExport16") != std::string::npos){
             this->sixteenthLTM = longtermMemory;
             ARMARX_INFO << "added LTM 16";
-        } */else {
+        } else {
             ARMARX_WARNING << "trying to add a LTM to the MemoryToIceAdapter with an export name "
                               "that does not correspond to a predefined LTM. Currently supported "
                               "LTMs are MemoryExport2, MemoryExport3, MemoryExport4, MemoryExport5 to 16. "
                               "You tried to add " << name;
         }
+        */
 
 
     }
@@ -261,7 +263,7 @@ namespace armarx::armem::server
                     longtermMemory->store(m);
                 }
 
-
+                /*
                 if(secondLTM->isRecording()){
                     armem::wm::Memory mem(secondLTM->name());
                     armem::wm::toMemory(mem, updateResult.removedSnapshots);
@@ -331,7 +333,7 @@ namespace armarx::armem::server
 
                     eleventhLTM->store(mem);
                 }
-                /*
+
 
                 if(twelvethLTM->isRecording()){
                     armem::wm::Memory mem(twelvethLTM->name());
@@ -526,6 +528,7 @@ namespace armarx::armem::server
         ARMARX_IMPORTANT << "Enabling the recording of memory " << longtermMemory->id().str();
         std::vector<armarx::armem::server::ltm::Memory*> ltms;
         ltms.push_back(longtermMemory);
+        /*
         ltms.push_back(secondLTM);
         ltms.push_back(thirdLTM);
         ltms.push_back(fourthLTM);
@@ -536,7 +539,7 @@ namespace armarx::armem::server
         ltms.push_back(ninthLTM);
         ltms.push_back(tenthLTM);
         ltms.push_back(eleventhLTM);
-        /*
+
         ltms.push_back(twelvethLTM);
         ltms.push_back(thirteenthLTM);
         ltms.push_back(fourteenthLTM);
@@ -564,6 +567,7 @@ namespace armarx::armem::server
         //all LTMs will be disabled:
         std::vector<armarx::armem::server::ltm::Memory*> ltms;
         ltms.push_back(longtermMemory);
+        /*
         ltms.push_back(secondLTM);
         ltms.push_back(thirdLTM);
         ltms.push_back(fourthLTM);
@@ -574,7 +578,6 @@ namespace armarx::armem::server
         ltms.push_back(ninthLTM);
         ltms.push_back(tenthLTM);
         ltms.push_back(eleventhLTM);
-        /*
         ltms.push_back(twelvethLTM);
         ltms.push_back(thirteenthLTM);
         ltms.push_back(fourteenthLTM);
diff --git a/source/RobotAPI/libraries/armem/server/MemoryToIceAdapter.h b/source/RobotAPI/libraries/armem/server/MemoryToIceAdapter.h
index 8185bea42..73e1dd6b6 100644
--- a/source/RobotAPI/libraries/armem/server/MemoryToIceAdapter.h
+++ b/source/RobotAPI/libraries/armem/server/MemoryToIceAdapter.h
@@ -64,6 +64,7 @@ namespace armarx::armem::server
     public:
         server::wm::Memory* workingMemory;
         server::ltm::Memory* longtermMemory;
+        /*
         server::ltm::Memory* secondLTM;
         server::ltm::Memory* thirdLTM;
         server::ltm::Memory* fourthLTM;
@@ -74,7 +75,6 @@ namespace armarx::armem::server
         server::ltm::Memory* ninthLTM;
         server::ltm::Memory* tenthLTM;
         server::ltm::Memory* eleventhLTM;
-        /*
         server::ltm::Memory* twelvethLTM;
         server::ltm::Memory* thirteenthLTM;
         server::ltm::Memory* fourteenthLTM;
diff --git a/source/RobotAPI/libraries/armem/server/ltm/Memory.cpp b/source/RobotAPI/libraries/armem/server/ltm/Memory.cpp
index 5f007c028..40c933521 100644
--- a/source/RobotAPI/libraries/armem/server/ltm/Memory.cpp
+++ b/source/RobotAPI/libraries/armem/server/ltm/Memory.cpp
@@ -18,8 +18,27 @@ namespace armarx::armem::server::ltm
         MongoDBStorageMixin::configureMixin(json);
     }
 
-    Memory::Memory() : Memory(std::filesystem::path("/tmp"), {}, "MemoryExport", "Test")
+    Memory::Memory() : Memory(std::filesystem::path("/tmp/ARMARX/LTM_Exports"), {}, "MemoryExport", "Test")
     {
+        //set path to include date of creation as prefix:
+        this->current_date = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+        std::tm* localTime = std::localtime(&this->current_date);
+
+        // convert current time into string:
+        std::stringstream ss;
+        ss << std::put_time(localTime, "%Y_%m_%d");
+        std::string dateString = ss.str();
+
+        // change memory base path to include current date (date of memory creation)
+        std::filesystem::path current_base_path = this->getMemoryBasePath();
+        this->setMemoryBasePath(current_base_path.append(dateString));
+
+        //inform user about change:
+        ARMARX_DEBUG << "Changed memory base path to include current date of "
+                    << dateString
+                    << " to "
+                    << this->getMemoryBasePath()
+                       ;
     }
 
     Memory::Memory(const detail::mixin::Path& p,
@@ -42,6 +61,7 @@ namespace armarx::armem::server::ltm
         MongoDBStorageMixin(s, exportName, MemoryID(memoryName, ""))
     {
         ARMARX_INFO << "Creating a new memory at " << p.string() << " and " << exportName;
+
     }
 
     void
diff --git a/source/RobotAPI/libraries/armem/server/ltm/Memory.h b/source/RobotAPI/libraries/armem/server/ltm/Memory.h
index 30e1929dd..effbee317 100644
--- a/source/RobotAPI/libraries/armem/server/ltm/Memory.h
+++ b/source/RobotAPI/libraries/armem/server/ltm/Memory.h
@@ -52,5 +52,8 @@ namespace armarx::armem::server::ltm
         void _resolve(armem::wm::Memory&) final;
         void _store(const armem::wm::Memory&) final;
         void _directlyStore(const armem::wm::Memory&) final;
+
+    private:
+        std::time_t current_date;
     };
 } // namespace armarx::armem::server::ltm
diff --git a/source/RobotAPI/libraries/armem/server/ltm/detail/MemoryBase.h b/source/RobotAPI/libraries/armem/server/ltm/detail/MemoryBase.h
index 377d94984..0719a427b 100644
--- a/source/RobotAPI/libraries/armem/server/ltm/detail/MemoryBase.h
+++ b/source/RobotAPI/libraries/armem/server/ltm/detail/MemoryBase.h
@@ -190,6 +190,11 @@ namespace armarx::armem::server::ltm::detail
         {
             defs->optional(p.enabled_on_startup, prefix + "enabled");
             defs->optional(p.configuration_on_startup, prefix + "configuration");
+            defs->optional(p.export_name, prefix + "exportName");
+            defs->optional(p.export_path, prefix + "exportPath");
+
+            ARMARX_INFO << "Setting property definitions";
+            _setExportName(p.export_name);
         }
 
         /// enable/disable
@@ -277,6 +282,8 @@ namespace armarx::armem::server::ltm::detail
             std::string configuration_on_startup =
                 "{ \"SnapshotFrequencyFilter\": {\"WaitingTimeInMsForFilter\" : 3000}"
                 "}";
+            std::string export_name ="MemoryExportMB";
+            std::string export_path = "/tmp";
         } p;
 
     protected:
diff --git a/source/RobotAPI/libraries/armem/server/ltm/detail/MemoryItem.h b/source/RobotAPI/libraries/armem/server/ltm/detail/MemoryItem.h
index bf212bb37..821f131bb 100644
--- a/source/RobotAPI/libraries/armem/server/ltm/detail/MemoryItem.h
+++ b/source/RobotAPI/libraries/armem/server/ltm/detail/MemoryItem.h
@@ -23,12 +23,12 @@ namespace armarx::armem::server::ltm::detail
         void setMemoryID(const MemoryID&);
         void setMemoryName(const std::string& memoryName);
 
-        std::string
-        getExportName() const
+        virtual std::string getExportName() const
         {
             return exportName;
         }
 
+
         MemoryID
         getMemoryID() const
         {
@@ -54,7 +54,7 @@ namespace armarx::armem::server::ltm::detail
         std::shared_ptr<Processors> processors;
 
     private:
-        std::string exportName = "MemoryExport";
+        std::string exportName = "MemoryExportMI";
         MemoryID _id;
     };
 } // namespace armarx::armem::server::ltm::detail
diff --git a/source/RobotAPI/libraries/armem/server/ltm/detail/mixins/DiskStorageMixin.cpp b/source/RobotAPI/libraries/armem/server/ltm/detail/mixins/DiskStorageMixin.cpp
index baed67a4c..647c9025b 100644
--- a/source/RobotAPI/libraries/armem/server/ltm/detail/mixins/DiskStorageMixin.cpp
+++ b/source/RobotAPI/libraries/armem/server/ltm/detail/mixins/DiskStorageMixin.cpp
@@ -21,7 +21,7 @@ namespace armarx::armem::server::ltm::detail::mixin
                                              const armem::MemoryID& id) :
         memoryBasePath(memoryParentPath), exportName(exportName), _id(id)
     {
-        //ARMARX_INFO << "creating DiskMemoryItemMixin";
+        //ARMARX_INFO << "creating DiskMemoryItemMixin with name " << exportName;
         //ARMARX_INFO << VAROUT(_id);
     }
 
@@ -34,6 +34,7 @@ namespace armarx::armem::server::ltm::detail::mixin
     void
     DiskMemoryItemMixin::setMixinExportName(const std::string& n)
     {
+        //ARMARX_INFO << "Currently setting export name to: " << n;
         exportName = n;
     }
 
@@ -71,6 +72,13 @@ namespace armarx::armem::server::ltm::detail::mixin
         return util::fs::toPath(p, cleanID);
     }
 
+    /*
+    std::string DiskMemoryItemMixin::getExportName() const
+    {
+        return exportName;
+    }
+    **/
+
     bool
     DiskMemoryItemMixin::memoryBasePathExists() const
     {
diff --git a/source/RobotAPI/libraries/armem/server/ltm/detail/mixins/DiskStorageMixin.h b/source/RobotAPI/libraries/armem/server/ltm/detail/mixins/DiskStorageMixin.h
index b9550d411..fc87e06c0 100644
--- a/source/RobotAPI/libraries/armem/server/ltm/detail/mixins/DiskStorageMixin.h
+++ b/source/RobotAPI/libraries/armem/server/ltm/detail/mixins/DiskStorageMixin.h
@@ -27,6 +27,7 @@ namespace armarx::armem::server::ltm::detail::mixin
 
         Path getMemoryBasePath() const;
         Path getFullPath() const;
+        //std::string getExportName() const;
 
         // Filesystem interaction
         bool memoryBasePathExists() const;
diff --git a/source/RobotAPI/libraries/armem/server/plugins/Plugin.cpp b/source/RobotAPI/libraries/armem/server/plugins/Plugin.cpp
index bdb594c02..a3bb52a64 100644
--- a/source/RobotAPI/libraries/armem/server/plugins/Plugin.cpp
+++ b/source/RobotAPI/libraries/armem/server/plugins/Plugin.cpp
@@ -42,6 +42,7 @@ namespace armarx::armem::server::plugins
             properties->optional(
                 workingMemory.name(), prefix + "MemoryName", "Name of this memory server.");
         }
+        /*
 
         // stuff for ltm        
         this->iceAdapter.addSecondLTM(&secondLTM);
@@ -54,7 +55,7 @@ namespace armarx::armem::server::plugins
         this->iceAdapter.addSecondLTM(&ninthLTM);
         this->iceAdapter.addSecondLTM(&tenthLTM);
         this->iceAdapter.addSecondLTM(&eleventhLTM);
-        /*
+
         this->iceAdapter.addSecondLTM(&twelvethLTM);
         this->iceAdapter.addSecondLTM(&thirteenthLTM);
         this->iceAdapter.addSecondLTM(&fourteenthLTM);
@@ -64,6 +65,7 @@ namespace armarx::armem::server::plugins
 
 
         longtermMemory.createPropertyDefinitions(properties, prefix + "ltm.");
+        /*
         secondLTM.createPropertyDefinitions(properties, prefix + "ltm2.");
         thirdLTM.createPropertyDefinitions(properties, prefix + "ltm3.");
         fourthLTM.createPropertyDefinitions(properties, prefix + "ltm4.");
@@ -74,7 +76,7 @@ namespace armarx::armem::server::plugins
         ninthLTM.createPropertyDefinitions(properties, prefix + "ltm9.");
         tenthLTM.createPropertyDefinitions(properties, prefix + "ltm10.");
         eleventhLTM.createPropertyDefinitions(properties, prefix + "ltm11.");
-        /*
+
         twelvethLTM.createPropertyDefinitions(properties, prefix + "ltm12.");
         thirteenthLTM.createPropertyDefinitions(properties, prefix + "ltm13.");
         fourteenthLTM.createPropertyDefinitions(properties, prefix + "ltm14.");
@@ -82,6 +84,8 @@ namespace armarx::armem::server::plugins
         sixteenthLTM.createPropertyDefinitions(properties, prefix + "ltm16.");
         */
 
+
+
     }
 
     void
@@ -96,12 +100,14 @@ namespace armarx::armem::server::plugins
     void
     Plugin::postOnInitComponent()
     {
+        /*
         ARMARX_INFO << VAROUT(secondLTM.p.configuration_on_startup);
         ARMARX_INFO << VAROUT(longtermMemory.p.configuration_on_startup);
         ARMARX_INFO << VAROUT(thirdLTM.p.configuration_on_startup);
         ARMARX_INFO << VAROUT(fourthLTM.p.configuration_on_startup);
         ARMARX_INFO << VAROUT(fiftLTM.p.configuration_on_startup);
         ARMARX_INFO << VAROUT(tenthLTM.p.configuration_on_startup);
+        */
         Component& parent = this->parent<Component>();
 
         // activate LTM
@@ -109,6 +115,7 @@ namespace armarx::armem::server::plugins
         if (not workingMemory.id().memoryName.empty())
         {
             longtermMemory.setMemoryID(workingMemory.id());
+            /*
             secondLTM.setMemoryID(MemoryID(workingMemory.id().memoryName));
             thirdLTM.setMemoryID(MemoryID(workingMemory.id().memoryName));
             fourthLTM.setMemoryID(MemoryID(workingMemory.id().memoryName));
@@ -119,7 +126,7 @@ namespace armarx::armem::server::plugins
             ninthLTM.setMemoryID(MemoryID(workingMemory.id().memoryName));
             tenthLTM.setMemoryID(MemoryID(workingMemory.id().memoryName));
             eleventhLTM.setMemoryID(MemoryID(workingMemory.id().memoryName));
-            /*
+
             twelvethLTM.setMemoryID(MemoryID(workingMemory.id().memoryName));
             thirteenthLTM.setMemoryID(MemoryID(workingMemory.id().memoryName));
             fourteenthLTM.setMemoryID(MemoryID(workingMemory.id().memoryName));
@@ -130,6 +137,7 @@ namespace armarx::armem::server::plugins
         else
         {
             longtermMemory.setMemoryID(MemoryID(parent.getDefaultName(), ""));
+            /*
             secondLTM.setMemoryID(MemoryID(parent.getDefaultName(), ""));
             thirdLTM.setMemoryID(MemoryID(parent.getDefaultName(), ""));
             fourthLTM.setMemoryID(MemoryID(parent.getDefaultName(), ""));
@@ -140,7 +148,7 @@ namespace armarx::armem::server::plugins
             ninthLTM.setMemoryID(MemoryID(parent.getDefaultName(), ""));
             tenthLTM.setMemoryID(MemoryID(parent.getDefaultName(), ""));
             eleventhLTM.setMemoryID(MemoryID(parent.getDefaultName(), ""));
-            /*
+
             twelvethLTM.setMemoryID(MemoryID(parent.getDefaultName(), ""));
             thirteenthLTM.setMemoryID(MemoryID(parent.getDefaultName(), ""));
             fourteenthLTM.setMemoryID(MemoryID(parent.getDefaultName(), ""));
@@ -149,6 +157,7 @@ namespace armarx::armem::server::plugins
             */
         }
         longtermMemory.configure();
+        /*
         secondLTM.configure();
         thirdLTM.configure();
         fourthLTM.configure();
@@ -159,7 +168,7 @@ namespace armarx::armem::server::plugins
         ninthLTM.configure();
         tenthLTM.configure();
         eleventhLTM.configure();
-        /*
+
         twelvethLTM.configure();
         thirteenthLTM.configure();
         fourteenthLTM.configure();
@@ -167,6 +176,9 @@ namespace armarx::armem::server::plugins
         sixteenthLTM.configure();
         */
 
+
+        //ARMARX_INFO << VAROUT(longtermMemory.p);
+
         initialized = true;
     }
 
@@ -184,6 +196,7 @@ namespace armarx::armem::server::plugins
         iceAdapter.setMemoryListener(memoryTopic);
 
         connected = true;
+        ARMARX_INFO << "Export Name: " << longtermMemory.getExportName(); //TODO: remove after testing
     }
 
     void
@@ -192,6 +205,7 @@ namespace armarx::armem::server::plugins
         ARMARX_INFO << "Preparing to save statistics for " << this->workingMemory.name();
         try{
             auto first_stats = longtermMemory.getFilterStatistics();
+            /*
             auto stats = secondLTM.getFilterStatistics();
             auto thirdStats = thirdLTM.getFilterStatistics();
             auto fourthStats = fourthLTM.getFilterStatistics();
@@ -202,7 +216,7 @@ namespace armarx::armem::server::plugins
             auto stats9 = ninthLTM.getFilterStatistics();
             auto stats10 = tenthLTM.getFilterStatistics();
             auto stats11 = eleventhLTM.getFilterStatistics();
-            /*
+
             auto stats12 = twelvethLTM.getFilterStatistics();
             auto stats13 = thirteenthLTM.getFilterStatistics();
             auto stats14 = fourteenthLTM.getFilterStatistics();
@@ -213,6 +227,7 @@ namespace armarx::armem::server::plugins
                 std::map<std::string, armarx::core::time::DateTime> times;
                 times["Started LTM1"] = longtermMemory.getStatistics().firstStarted;
                 times["Stopped LTM1"] = longtermMemory.getStatistics().firstStopped;
+                /*
                 times["Started LTM2"] = secondLTM.getStatistics().firstStarted;
                 times["Stopped LTM2"] = secondLTM.getStatistics().firstStopped;
                 times["Started LTM3"] = thirdLTM.getStatistics().firstStarted;
@@ -233,7 +248,7 @@ namespace armarx::armem::server::plugins
                 times["Stopped LTM10"] = tenthLTM.getStatistics().firstStopped;
                 times["Started LTM11"] = eleventhLTM.getStatistics().firstStarted;
                 times["Stopped LTM11"] = eleventhLTM.getStatistics().firstStopped;
-                /*
+
                 times["Started LTM12"] = twelvethLTM.getStatistics().firstStarted;
                 times["Stopped LTM12"] = twelvethLTM.getStatistics().firstStopped;
                 times["Started LTM13"] = thirteenthLTM.getStatistics().firstStarted;
@@ -246,7 +261,8 @@ namespace armarx::armem::server::plugins
                 times["Stopped LTM16"] = sixteenthLTM.getStatistics().firstStopped;
                 */
                 std::map<std::string, std::map<std::string, ltm::processor::SnapshotFilter::FilterStatistics>> information;
-                information["LTM1"] = first_stats;
+                information["LTM"] = first_stats;
+                /*
                 if(stats.size() > 0){
                     information["LTM2"] = stats;
                 }
@@ -277,7 +293,7 @@ namespace armarx::armem::server::plugins
                 if(stats11.size() > 0){
                     information["LTM11"] = stats11;
                 }
-                /*
+
                 if(stats12.size() > 0){
                     information["LTM12"] = stats12;
                 }
@@ -295,7 +311,7 @@ namespace armarx::armem::server::plugins
                 }
 
                 */
-                test::save_statistics(information, times, secondLTM.name());
+                //test::save_statistics(information, times, secondLTM.name());
             } catch(...){
                 ARMARX_INFO << "Something went wrong after getting the statistics";
             }
diff --git a/source/RobotAPI/libraries/armem/server/plugins/Plugin.h b/source/RobotAPI/libraries/armem/server/plugins/Plugin.h
index 10c3a73ee..2037cb44e 100644
--- a/source/RobotAPI/libraries/armem/server/plugins/Plugin.h
+++ b/source/RobotAPI/libraries/armem/server/plugins/Plugin.h
@@ -79,6 +79,7 @@ namespace armarx::armem::server::plugins
 
         /// A manager class for the ltm. It internally holds a normal wm instance as a cache.
         server::ltm::Memory longtermMemory;
+        /*
         server::ltm::Memory secondLTM = server::ltm::Memory(std::filesystem::path("/tmp"), {}, "MemoryExport2", "LTM2");
         server::ltm::Memory thirdLTM = server::ltm::Memory(std::filesystem::path("/tmp"), {}, "MemoryExport3", "LTM3");
         server::ltm::Memory fourthLTM = server::ltm::Memory(std::filesystem::path("/tmp"), {}, "MemoryExport4", "LTM4");
@@ -89,7 +90,7 @@ namespace armarx::armem::server::plugins
         server::ltm::Memory ninthLTM = server::ltm::Memory(std::filesystem::path("/tmp"), {}, "MemoryExport9", "LTM9");
         server::ltm::Memory tenthLTM = server::ltm::Memory(std::filesystem::path("/tmp"), {}, "MemoryExport10", "LTM10");
         server::ltm::Memory eleventhLTM = server::ltm::Memory(std::filesystem::path("/tmp"), {}, "MemoryExport11", "LTM11");
-        /*
+
         server::ltm::Memory twelvethLTM = server::ltm::Memory(std::filesystem::path("/tmp"), {}, "MemoryExport12", "LTM12");
         server::ltm::Memory thirteenthLTM = server::ltm::Memory(std::filesystem::path("/tmp"), {}, "MemoryExport13", "LTM13");
         server::ltm::Memory fourteenthLTM = server::ltm::Memory(std::filesystem::path("/tmp"), {}, "MemoryExport14", "LTM14");
-- 
GitLab