Composable tlbmc

This CL made modules in tlBMC flexible and configurable at runtime. See designs in go/composable-tlbmc

Changes are:

1. Make collectors composable. For a collector, if it is not enabled, its equivalent dummy collector is created. A dummy collector will return empty resources or not found on its interfaces. This is needed as the code base does not do nullptr check today.
1. Defined a central config proto, so that collector are enabled by configs. Every platform can have its unique config
1. Platform is determined at tlbmc runtime, no need for to specify platform at compile time
1. A single textproto file is created to configure tlbmc across platforms. This is to ensure the WIP gbmcweb msvfud package can still support single package for every platform. This textproto file will be maintained in https://gbmc-private.googlesource.com/ as I can't leak code names.
1. Made a factory method so that the logic of creating tlbmc App and store can be tested. Today it's in webserver_main_include and it is not tested.
1. Made a `default` platform which represents all platform that have `tlbmc` enabled but without FRU collector and Sensor collector turned on. Added e2e tests for it

#tlbmc

PiperOrigin-RevId: 780235972
Change-Id: Ibefb10261c36af674f00cb450d157918daec7aeb
diff --git a/g3/server.cc b/g3/server.cc
index 0a8de77..d235a09 100644
--- a/g3/server.cc
+++ b/g3/server.cc
@@ -16,6 +16,7 @@
 #include "payload.pb.h"
 #include "sensor_identifier.pb.h"
 #include "sensor_payload.pb.h"
+#include "tlbmc/central_config/config.h"
 
 #pragma GCC diagnostic push
 #pragma GCC diagnostic warning \
@@ -75,11 +76,15 @@
 #include "nlohmann/json.hpp"
 #include "config_parser.h"
 #include "oauth_utils.h"
+#include "tlbmc/hal/fru_scanner_i2c.h"
 #include "tlbmc/hal/shared_mem/server.h"
+#include "tlbmc/hal/sysfs/i2c.h"
 #include "tlbmc/redfish/app.h"
 #include "tlbmc/redfish/request.h"
 #include "tlbmc/redfish/response.h"
+#include "tlbmc/redfish/routes/all_routes.h"
 #include "tlbmc/store/store_hft_adapter.h"
+#include "tlbmc/store/store_impl.h"
 #include "tlbmc/trace/tracer.h"
 #include "bmcweb_authorizer_singleton.h"
 #include "app.hpp"
@@ -950,6 +955,56 @@
   LOG(WARNING) << "gRPC server stopped!";
 }
 
+std::unique_ptr<milotic_tlbmc::RedfishApp> GrpcRedfishService::CreateTlbmcApp(
+    const RedfishServiceConfig& config) {
+  using ::milotic_tlbmc::FruScannerI2c;
+  using ::milotic_tlbmc::I2cSysfs;
+  using ::milotic_tlbmc::RedfishApp;
+  using ::milotic_tlbmc::StoreImpl;
+  using ::milotic_tlbmc::Tracer;
+  using ::milotic_tlbmc::InitializeTlbmcConfig;
+  InitializeTlbmcConfig(config.tlbmc_config_bundle_path,
+                        config.tlbmc_release_file_path);
+  std::unique_ptr<RedfishApp> tlbmc_app = nullptr;
+  // Initialize tlbmc
+  if (config.enable_tlbmc_trace) {
+    Tracer::Initialize(true);
+  }
+  milotic_tlbmc::I2cSysfs i2c_sysfs(milotic_tlbmc::I2cSysfsConfig{});
+  std::unique_ptr<milotic_tlbmc::FruScannerI2c> fru_scanner =
+      milotic_tlbmc::FruScannerI2c::Create({});
+  bool enable_tlbmc = config.enable_tlbmc;
+  std::string disable_tlbmc_file_path = config.tlbmc_disable_file;
+  if (std::filesystem::exists(disable_tlbmc_file_path)) {
+    LOG(WARNING) << "Forcefully disable tlbmc";
+    enable_tlbmc = false;
+  }
+  if (enable_tlbmc) {
+    milotic_tlbmc::StoreImpl::Options options;
+    options.fru_scanners = {fru_scanner.get()};
+    options.i2c_sysfs = &i2c_sysfs;
+    options.config_location = config.tlbmc_entity_config_location;
+
+    milotic_tlbmc::SharedMemoryServer::Initialize();
+
+    absl::StatusOr<std::unique_ptr<milotic_tlbmc::StoreImpl>> tlbmc_store =
+        milotic_tlbmc::StoreImpl::Create(options);
+    if (tlbmc_store.ok()) {
+      tlbmc_app =
+          std::make_unique<milotic_tlbmc::RedfishApp>(std::move(*tlbmc_store));
+      milotic_tlbmc::RegisterAllRoutes(*tlbmc_app);
+      tlbmc_app->Validate();
+    } else {
+      LOG(ERROR) << "Cannot create tlBMC store!! Error: "
+                 << tlbmc_store.status() << " - Disabling tlBMC.";
+      tlbmc_app = nullptr;
+      enable_tlbmc = false;
+    }
+  }
+  LOG(WARNING) << "tlbmc enabled: " << (enable_tlbmc ? "true" : "false");
+  return tlbmc_app;
+}
+
 GrpcRedfishService::GrpcRedfishService(
     App* app, const milotic_tlbmc::RedfishApp* tlbmc_app,
     const std::shared_ptr<boost::asio::io_context>& io_context_main_thread,
@@ -1024,8 +1079,7 @@
 
   std::unique_ptr<SubscriptionManager> subscription_manager;
   if (config_.enable_hft_fake_manager) {
-    auto fake_manager =
-        std::make_unique<SubscriptionManagerFake>();
+    auto fake_manager = std::make_unique<SubscriptionManagerFake>();
     Payload payload;
     HighFrequencySensorsReadings* readings =
         payload.mutable_high_frequency_sensors_readings_batch()
diff --git a/g3/server.h b/g3/server.h
index c9f7a29..abc99c8 100644
--- a/g3/server.h
+++ b/g3/server.h
@@ -59,6 +59,12 @@
   std::string offline_node_entity_path =
       "/var/google/googlemachineidentity/live/offline_node_entities.pb";
   std::string authority_policy_file_binary = "/var/google/loas3/policy.pb";
+  std::string tlbmc_entity_config_location =
+      "/usr/share/entity-manager/configurations";
+  std::string tlbmc_disable_file = "/var/google/tlbmc/disable_tlbmc";
+  std::string tlbmc_config_bundle_path =
+      "/var/google/tlbmc/tlbmc_config_bundle.textproto";
+  std::string tlbmc_release_file_path = "/etc/os-release";
 
   // Enable multi-threading for the GET requests.
   bool multi_thread_get = false;
@@ -99,6 +105,9 @@
 
   // Enable HFT fake subscription manager.
   bool enable_hft_fake_manager = false;
+
+  // Enable TLBMC trace.
+  bool enable_tlbmc_trace = false;
 };
 
 // Checks server's creds state and request metadata/headers
@@ -110,6 +119,10 @@
 
 class GrpcRedfishService {
  public:
+  // Creates the TlBMC app.
+  static std::unique_ptr<milotic_tlbmc::RedfishApp> CreateTlbmcApp(
+      const RedfishServiceConfig& config);
+
   // For server with no multi-thread support.
   GrpcRedfishService(
       App& app,
diff --git a/tlbmc/central_config.proto b/tlbmc/central_config.proto
new file mode 100644
index 0000000..e54cd95
--- /dev/null
+++ b/tlbmc/central_config.proto
@@ -0,0 +1,38 @@
+edition = "2023";
+
+package milotic_tlbmc;
+
+// Controls the FRU collector module.
+message FruCollectorModule {
+  bool enabled = 1 [default = false];
+  // If true, allow FRUs not mapping to a config. This is useful for testing.
+  // This should not be enabled in production: if a FRU does not map to a
+  // config, very likely the machine has unsupported hardware config. Making
+  // tlbmc own the chassis of the machine may cause incorrect links.
+  bool allow_dangling_frus = 2 [default = false];
+}
+
+// Controls the sensor collector module.
+// Note, sensor collector module needs FRU collector module to be enabled.
+message SensorCollectorModule {
+  bool enabled = 1 [default = false];
+  // If true, allow sensor creation failures to not end tlBMC store creation.
+  // This is useful for testing. This should not be enabled in production: if
+  // sensor creation fails, very likely the machine has real hardware issues.
+  bool allow_sensor_creation_failure = 2 [default = false];
+}
+
+// A proto message to hold all configurations of the modules in tlbmc.
+message TlbmcConfig {
+  FruCollectorModule fru_collector_module = 1;
+  SensorCollectorModule sensor_collector_module = 2;
+}
+
+message TlbmcConfigBundle {
+  TlbmcConfig general_config = 1;
+  // If a platform is specified in the map, the corresponding config will be
+  // used.
+  // Otherwise, the general config will be used.
+  // The key is the platform name, and the value is the config for the platform.
+  map<string, TlbmcConfig> platform_to_config = 2;
+}
diff --git a/tlbmc/central_config/config.cc b/tlbmc/central_config/config.cc
index d39df50..92fecd1 100644
--- a/tlbmc/central_config/config.cc
+++ b/tlbmc/central_config/config.cc
@@ -1,16 +1,101 @@
 #include "tlbmc/central_config/config.h"
 
+#include <fstream>
+#include <string>
+#include <string_view>
+
+#include "absl/base/no_destructor.h"
+#include "absl/log/log.h"
+#include "central_config.pb.h"
+#include "google/protobuf/io/zero_copy_stream_impl.h"
+#include "google/protobuf/text_format.h"
+
 namespace milotic_tlbmc {
 
-TlbmcConfig& InitializeTlbmcConfig(const TlbmcConfig& config) {
-  static TlbmcConfig static_config(config);
-  return static_config;
+namespace internal {
+
+constexpr const char* kRofsConfigBundlePath =
+    "/etc/tlbmc/tlbmc_config_bundle.textproto";
+
+using ::google::protobuf::TextFormat;
+using ::google::protobuf::io::IstreamInputStream;
+
+std::string GetPlatformName(const std::string& release_file_path) {
+  std::ifstream release_file(release_file_path);
+  if (!release_file.is_open()) {
+    LOG(WARNING) << "Failed to open release file at '" << release_file_path
+                 << "'. Using default platform name.";
+    return "";
+  }
+  std::string read_line;
+  std::string platform_name;
+  while (std::getline(release_file, read_line)) {
+    constexpr std::string_view kPlatformNamePrefix = "OPENBMC_TARGET_MACHINE=";
+    if (!read_line.starts_with(kPlatformNamePrefix)) {
+      continue;
+    }
+    platform_name = read_line.substr(kPlatformNamePrefix.size());
+    break;
+  }
+  // Remove the quotes if any.
+  if (platform_name.size() > 2 && platform_name.front() == '"' &&
+      platform_name.back() == '"') {
+    platform_name = platform_name.substr(1, platform_name.size() - 2);
+  }
+  return platform_name;
 }
 
-const TlbmcConfig& GetTlbmcConfig() { return InitializeTlbmcConfig({}); }
+TlbmcConfig MergeConfig(const TlbmcConfig& general_config,
+                        const TlbmcConfig& platform_config) {
+  TlbmcConfig merged_config = general_config;
+  merged_config.MergeFrom(platform_config);
+  return merged_config;
+}
+
+TlbmcConfig GetConfigFromPathOrFallbackToDefault(
+    const std::string& config_bundle_path,
+    const std::string& release_file_path) {
+  // Try to open the config bundle file from the given path. If it fails, try to
+  // open the rofs config bundle file. If both fail, return the default config.
+  std::ifstream config_file(config_bundle_path);
+  if (!config_file.is_open()) {
+    LOG(WARNING) << "Failed to open config bundle file at '"
+                 << config_bundle_path << "'. Using default config.";
+    config_file = std::ifstream(kRofsConfigBundlePath);
+    if (!config_file.is_open()) {
+      LOG(WARNING) << "Failed to open rofs config bundle file at '"
+                   << kRofsConfigBundlePath << "'. Using default config.";
+      return TlbmcConfig();
+    }
+  }
+  TlbmcConfigBundle config_bundle;
+  IstreamInputStream istream_source(&config_file);
+  if (!TextFormat::Parse(&istream_source, &config_bundle)) {
+    LOG(WARNING) << "Failed to parse config bundle from file: '"
+                 << config_bundle_path << "'. Using default config.";
+    return TlbmcConfig();
+  }
+  std::string platform_name = GetPlatformName(release_file_path);
+  if (auto it = config_bundle.platform_to_config().find(platform_name);
+      it != config_bundle.platform_to_config().end()) {
+    return MergeConfig(config_bundle.general_config(), it->second);
+  }
+  return config_bundle.general_config();
+}
+}  // namespace internal
+
+TlbmcConfig& InitializeTlbmcConfig(const std::string& config_bundle_path,
+                                   const std::string& release_file_path) {
+  static absl::NoDestructor<TlbmcConfig> static_config(
+      internal::GetConfigFromPathOrFallbackToDefault(config_bundle_path,
+                                                     release_file_path));
+  return *static_config;
+}
+
+const TlbmcConfig& GetTlbmcConfig() { return InitializeTlbmcConfig("", ""); }
 
 TlbmcConfig& GetMutableTlbmcConfigForTest() {
-  return InitializeTlbmcConfig({});
+  return InitializeTlbmcConfig("", "");
 }
 
 }  // namespace milotic_tlbmc
diff --git a/tlbmc/central_config/config.h b/tlbmc/central_config/config.h
index d2eec46..622c5ae 100644
--- a/tlbmc/central_config/config.h
+++ b/tlbmc/central_config/config.h
@@ -1,40 +1,48 @@
 #ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_CENTRAL_CONFIG_CONFIG_H_
 #define THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_CENTRAL_CONFIG_CONFIG_H_
 
+#include <string>
+
+#include "central_config.pb.h"
+
 namespace milotic_tlbmc {
 
-// A simple struct to hold all configurations for tlbmc.
+namespace internal {
+// Merges the general config with the platform overrides.
+TlbmcConfig MergeConfig(const TlbmcConfig& general_config,
+                        const TlbmcConfig& platform_config);
+// Parses the config from the given path to a `TlbmcConfigBundle` textproto or
+// the rofs config bundle file if the given path is invalid.
+// Then return the merged config for this platform.
+TlbmcConfig GetConfigFromPathOrFallbackToDefault(
+    const std::string& config_bundle_path,
+    const std::string& release_file_path);
+// Returns the platform name from the given release file path. If the release
+// file path is invalid, returns an empty string.
+std::string GetPlatformName(const std::string& release_file_path);
+}  // namespace internal
+
 // TlbmcConfig controls the behavior of tlbmc.
 // Typical usage:
 //   In the main function:
-//     InitializeTlbmcConfig(TlbmcConfig{
-//       .allow_dangling_frus = ...,
-//     });
+//     InitializeTlbmcConfig(*, *);
 //
 //   In the code:
-//   if (GetTlbmcConfig().allow_dangling_frus) {
+//   if (GetTlbmcConfig().fru_collector_module().enabled()) {
 //     // Handle dangling FRUs.
 //   }
 //
 //   In unit tests:
-//    GetMutableTlbmcConfigForTest().allow_dangling_frus = true;
+//    GetMutableTlbmcConfigForTest().mutable_fru_collector_module()
+//        ->set_allow_dangling_frus(true)
 //    // Test code.
-//    GetMutableTlbmcConfigForTest().allow_dangling_frus = false;
+//    GetMutableTlbmcConfigForTest().mutable_fru_collector_module()
+//        ->set_allow_dangling_frus(false)
 //    // Restore the original config.
-struct TlbmcConfig {
-  // If true, allow FRUs not mapping to a config. This is useful for testing.
-  // This should not be enabled in production: if a FRU does not map to a
-  // config, very likely the machine has unsupported hardware config. Making
-  // tlbmc own the chassis of the machine may cause incorrect links.
-  bool allow_dangling_frus = false;
-  // If true, allow sensor creation failures to not end tlBMC store creation.
-  // This is useful for testing. This should not be enabled in production: if
-  // sensor creation fails, very likely the machine has real hardware issues.
-  bool allow_sensor_creation_failure = false;
-};
+TlbmcConfig& InitializeTlbmcConfig(const std::string& config_bundle_path,
+                                   const std::string& release_file_path);
 
-TlbmcConfig& InitializeTlbmcConfig(const TlbmcConfig& config);
-
+// Returns the TlbmcConfig for this platform.
 const TlbmcConfig& GetTlbmcConfig();
 
 TlbmcConfig& GetMutableTlbmcConfigForTest();
diff --git a/tlbmc/collector/fru_collector.cc b/tlbmc/collector/fru_collector.cc
index c1068a3..3ba1312 100644
--- a/tlbmc/collector/fru_collector.cc
+++ b/tlbmc/collector/fru_collector.cc
@@ -11,6 +11,7 @@
 #include <utility>
 #include <vector>
 
+#include "absl/base/thread_annotations.h"
 #include "absl/container/flat_hash_map.h"
 #include "absl/functional/any_invocable.h"
 #include "absl/log/log.h"
@@ -410,4 +411,36 @@
   SetUpAdHocFruScanning();
 }
 
+std::unique_ptr<FruCollector> EmptyFruCollector::Create() {
+  return std::make_unique<EmptyFruCollector>();
+}
+
+RawFruTable EmptyFruCollector::GetCopyOfCurrentScannedFrus() const
+    ABSL_LOCKS_EXCLUDED(fru_table_mutex_) {
+  LOG(WARNING) << "EmptyFruCollector::GetCopyOfCurrentScannedFrus is called. "
+                  "This is a no-op.";
+  return RawFruTable();
+}
+
+nlohmann::json EmptyFruCollector::ToJson() const {
+  return nlohmann::json::parse("{\"Warning\": \"EmptyFruCollector used.\"}");
+}
+
+void EmptyFruCollector::SetEntityConfig(
+    const std::shared_ptr<EntityConfig>& entity_config)
+    ABSL_LOCKS_EXCLUDED(entity_config_mutex_) {
+  LOG(WARNING) << "EmptyFruCollector::SetEntityConfig is called. This is a "
+                  "no-op.";
+}
+
+std::shared_ptr<EntityConfig> EmptyFruCollector::GetEntityConfig() const {
+  return nullptr;
+}
+
+nlohmann::json EmptyFruCollector::GetSchedulerStats() const {
+  return nlohmann::json::parse("{\"Warning\": \"EmptyFruCollector used.\"}");
+}
+
+std::size_t EmptyFruCollector::GetAllUserTaskCount() const { return 0; }
+
 }  // namespace milotic_tlbmc
diff --git a/tlbmc/collector/fru_collector.h b/tlbmc/collector/fru_collector.h
index 918e584..eadfe7d 100644
--- a/tlbmc/collector/fru_collector.h
+++ b/tlbmc/collector/fru_collector.h
@@ -81,18 +81,19 @@
     return fru_table_;
   }
 
-  nlohmann::json ToJson() const override;
+  virtual nlohmann::json ToJson() const override;
 
   static absl::StatusOr<RawFru> CreateRawFruFromI2cFruInfo(
       const std::unique_ptr<I2cFruInfo>& i2c_fru);
 
-  void SetEntityConfig(const std::shared_ptr<EntityConfig>& entity_config)
+  virtual void SetEntityConfig(
+      const std::shared_ptr<EntityConfig>& entity_config)
       ABSL_LOCKS_EXCLUDED(entity_config_mutex_) {
     absl ::MutexLock lock(&entity_config_mutex_);
     entity_config_ = entity_config;
   }
 
-  std::shared_ptr<EntityConfig> GetEntityConfig() const
+  virtual std::shared_ptr<EntityConfig> GetEntityConfig() const
       ABSL_LOCKS_EXCLUDED(entity_config_mutex_) {
     absl::MutexLock lock(&entity_config_mutex_);
     return entity_config_;
@@ -102,7 +103,7 @@
     return task_scheduler_->ToJson();
   }
 
-  std::size_t GetAllUserTaskCount() const {
+  virtual std::size_t GetAllUserTaskCount() const {
     return task_scheduler_->GetAllUserTaskCount();
   }
 
@@ -184,6 +185,29 @@
       ABSL_GUARDED_BY(entity_config_mutex_);
 };
 
+// This class is used when the FruCollector module is disabled.
+class EmptyFruCollector final : public FruCollector {
+ public:
+  EmptyFruCollector() = default;
+
+  // Creates a dummy collector.
+  static std::unique_ptr<FruCollector> Create();
+  // Collects and stores FRU data.
+  RawFruTable GetCopyOfCurrentScannedFrus() const
+      ABSL_LOCKS_EXCLUDED(fru_table_mutex_) override;
+
+  nlohmann::json ToJson() const override;
+
+  void SetEntityConfig(const std::shared_ptr<EntityConfig>& entity_config)
+      override ABSL_LOCKS_EXCLUDED(entity_config_mutex_);
+
+  std::shared_ptr<EntityConfig> GetEntityConfig() const override;
+
+  nlohmann::json GetSchedulerStats() const override;
+
+  std::size_t GetAllUserTaskCount() const override;
+};
+
 }  // namespace milotic_tlbmc
 
 #endif  // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_COLLECTOR_FRU_COLLECTOR_H_
diff --git a/tlbmc/collector/sensor_collector.cc b/tlbmc/collector/sensor_collector.cc
index 6bbf9c0..e326b01 100644
--- a/tlbmc/collector/sensor_collector.cc
+++ b/tlbmc/collector/sensor_collector.cc
@@ -502,4 +502,57 @@
                                               params.refresh_notification));
 }
 
+std::unique_ptr<EmptySensorCollector> EmptySensorCollector::Create() {
+  return std::make_unique<EmptySensorCollector>();
+}
+
+// Returns the sorted list of sensor names contained by the given Config name.
+std::vector<std::string> EmptySensorCollector::GetAllSensorKeysByConfigName(
+    const std::string& board_config_name) const {
+  LOG(WARNING) << "EmptySensorCollector::GetAllSensorKeysByConfigName is "
+                  "called. This will return an empty list.";
+  return {};
+}
+
+// Returns all the sensors sorted by sensor name.
+std::vector<std::shared_ptr<const Sensor>> EmptySensorCollector::GetAllSensors()
+    const {
+  LOG(WARNING) << "EmptySensorCollector::GetAllSensorKeysByConfigName is "
+                  "called. This will return an empty list.";
+  return {};
+}
+
+// Returns the sensor for the given sensor key.
+std::shared_ptr<const Sensor> EmptySensorCollector::GetSensorBySensorKey(
+    const std::string& sensor_key) const {
+  LOG(WARNING) << "EmptySensorCollector::GetSensorBySensorKey is "
+                  "called. This will return a nullptr.";
+  return nullptr;
+}
+
+absl::Status EmptySensorCollector::ConfigureCollection(
+    const Config& config) const {
+  return absl::UnimplementedError(
+      "EmptySensorCollector::ConfigureCollection is called. This is not "
+      "implemented.");
+}
+
+std::shared_ptr<const Sensor>
+EmptySensorCollector::GetSensorByConfigNameAndSensorKey(
+    const std::string& board_config_name, const std::string& sensor_key) const {
+  LOG(WARNING) << "EmptySensorCollector::GetSensorByConfigNameAndSensorKey is "
+                  "called. This will return a nullptr.";
+  return nullptr;
+}
+
+nlohmann::json EmptySensorCollector::GetSchedulerStats() const {
+  return nlohmann::json::parse(
+      "{\"Warning\": \"EmptySensorCollector used.\"}");
+}
+
+nlohmann::json EmptySensorCollector::ToJson() const {
+  return nlohmann::json::parse(
+      "{\"Warning\": \"EmptySensorCollector used.\"}");
+}
+
 }  // namespace milotic_tlbmc
diff --git a/tlbmc/collector/sensor_collector.h b/tlbmc/collector/sensor_collector.h
index e12e531..60d5d13 100644
--- a/tlbmc/collector/sensor_collector.h
+++ b/tlbmc/collector/sensor_collector.h
@@ -134,6 +134,32 @@
   const SensorNotification* refresh_notification_;
 };
 
+class EmptySensorCollector final : public SensorCollector {
+ public:
+  static std::unique_ptr<EmptySensorCollector> Create();
+
+  // Returns the sorted list of sensor names contained by the given Config name.
+  std::vector<std::string> GetAllSensorKeysByConfigName(
+      const std::string& board_config_name) const override;
+
+  // Returns all the sensors sorted by sensor name.
+  std::vector<std::shared_ptr<const Sensor>> GetAllSensors() const override;
+
+  // Returns the sensor for the given sensor key.
+  std::shared_ptr<const Sensor> GetSensorBySensorKey(
+      const std::string& sensor_key) const override;
+
+  absl::Status ConfigureCollection(const Config& config) const override;
+
+  std::shared_ptr<const Sensor> GetSensorByConfigNameAndSensorKey(
+      const std::string& board_config_name,
+      const std::string& sensor_key) const override;
+
+  nlohmann::json GetSchedulerStats() const override;
+
+  nlohmann::json ToJson() const override;
+};
+
 }  // namespace milotic_tlbmc
 
 #endif  // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_COLLECTOR_SENSOR_COLLECTOR_H_
diff --git a/tlbmc/configs/entity_config.cc b/tlbmc/configs/entity_config.cc
new file mode 100644
index 0000000..c404d4a
--- /dev/null
+++ b/tlbmc/configs/entity_config.cc
@@ -0,0 +1,149 @@
+#include "tlbmc/configs/entity_config.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "nlohmann/json.hpp"
+#include "fan_controller_config.pb.h"
+#include "fan_pwm_config.pb.h"
+#include "fan_tach_config.pb.h"
+#include "hwmon_temp_sensor_config.pb.h"
+#include "psu_sensor_config.pb.h"
+#include "shared_mem_sensor_config.pb.h"
+#include "topology_config.pb.h"
+#include "fru.pb.h"
+#include "router_interface.h"
+
+namespace milotic_tlbmc {
+
+// Factory function definition
+std::unique_ptr<EntityConfig> EmptyEntityConfigImpl::Create() {
+  return std::make_unique<EmptyEntityConfigImpl>();
+}
+
+// EmptyEntityConfigImpl method implementations
+absl::StatusOr<absl::Span<const HwmonTempSensorConfig>>
+EmptyEntityConfigImpl::GetAllHwmonTempSensorConfigs() const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+}
+
+absl::StatusOr<absl::Span<const PsuSensorConfig>>
+EmptyEntityConfigImpl::GetAllPsuSensorConfigs() const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<absl::Span<const FanControllerConfig>>
+EmptyEntityConfigImpl::GetAllFanControllerConfigs() const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<absl::Span<const FanPwmConfig>>
+EmptyEntityConfigImpl::GetAllFanPwmConfigs() const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<absl::Span<const FanTachConfig>>
+EmptyEntityConfigImpl::GetAllFanTachConfigs() const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<absl::Span<const SharedMemSensorConfig>>
+EmptyEntityConfigImpl::GetAllSharedMemSensorConfigs() const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<const TopologyConfigNode*> EmptyEntityConfigImpl::GetFruTopology(
+    absl::string_view fru_key) const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<const TopologyConfigNode*>
+EmptyEntityConfigImpl::GetFruTopologyByConfig(
+    absl::string_view config_name) const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<const TopologyConfig*> EmptyEntityConfigImpl::GetTopologyConfig()
+    const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<std::vector<std::string>>
+EmptyEntityConfigImpl::GetAllConfigNames() const {
+  return std::vector<std::string>();
+}
+
+absl::StatusOr<std::string> EmptyEntityConfigImpl::GetConfigNameByFruKey(
+    absl::string_view fru_key) const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<std::string> EmptyEntityConfigImpl::GetFruKeyByConfigName(
+    absl::string_view config_name) const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<std::string> EmptyEntityConfigImpl::GetFruDevpath(
+    absl::string_view fru_key) const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<const Fru*> EmptyEntityConfigImpl::GetFru(
+    absl::string_view key) const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+absl::StatusOr<const FruTable*> EmptyEntityConfigImpl::GetAllFrus() const {
+  return absl::UnimplementedError(
+      "EmptyEntityConfigImpl used. This interface is unimplemented.");
+  ;
+}
+
+void EmptyEntityConfigImpl::UpdateFruAndTopology(const RawFruTable& fru_table) {
+  LOG(WARNING) << "EmptyEntityConfigImpl::UpdateFruAndTopology is called. This "
+                  "is a no-op.";
+}
+
+void EmptyEntityConfigImpl::SetSmartRouter(
+    ::crow::RouterInterface* smart_router) {
+  LOG(WARNING)
+      << "EmptyEntityConfigImpl::SetSmartRouter is called. This is a no-op.";
+}
+
+nlohmann::json EmptyEntityConfigImpl::ToJson() const {
+  return nlohmann::json::parse(
+      "{\"Warning\": \"EmptyEntityConfigImpl used.\"}");
+}
+
+}  // namespace milotic_tlbmc
diff --git a/tlbmc/configs/entity_config.h b/tlbmc/configs/entity_config.h
index d8bfcf9..e97a771 100644
--- a/tlbmc/configs/entity_config.h
+++ b/tlbmc/configs/entity_config.h
@@ -1,9 +1,11 @@
 #ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_CONFIG_ENTITY_CONFIG_H_
 #define THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_CONFIG_ENTITY_CONFIG_H_
 
+#include <memory>
 #include <string>
 #include <vector>
 
+#include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
@@ -68,6 +70,43 @@
   virtual ~EntityConfig() = default;
 };
 
+class EmptyEntityConfigImpl final : public EntityConfig {
+ public:
+  static std::unique_ptr<EntityConfig> Create();
+  EmptyEntityConfigImpl() = default;
+  ~EmptyEntityConfigImpl() override = default;
+
+  absl::StatusOr<absl::Span<const HwmonTempSensorConfig>>
+  GetAllHwmonTempSensorConfigs() const override;
+  absl::StatusOr<absl::Span<const PsuSensorConfig>> GetAllPsuSensorConfigs()
+      const override;
+  absl::StatusOr<absl::Span<const FanControllerConfig>>
+  GetAllFanControllerConfigs() const override;
+  absl::StatusOr<absl::Span<const FanPwmConfig>> GetAllFanPwmConfigs()
+      const override;
+  absl::StatusOr<absl::Span<const FanTachConfig>> GetAllFanTachConfigs()
+      const override;
+  absl::StatusOr<absl::Span<const SharedMemSensorConfig>>
+  GetAllSharedMemSensorConfigs() const override;
+  absl::StatusOr<const TopologyConfigNode*> GetFruTopology(
+      absl::string_view fru_key) const override;
+  absl::StatusOr<const TopologyConfigNode*> GetFruTopologyByConfig(
+      absl::string_view config_name) const override;
+  absl::StatusOr<const TopologyConfig*> GetTopologyConfig() const override;
+  absl::StatusOr<std::vector<std::string>> GetAllConfigNames() const override;
+  absl::StatusOr<std::string> GetConfigNameByFruKey(
+      absl::string_view fru_key) const override;
+  absl::StatusOr<std::string> GetFruKeyByConfigName(
+      absl::string_view config_name) const override;
+  absl::StatusOr<std::string> GetFruDevpath(
+      absl::string_view fru_key) const override;
+  absl::StatusOr<const Fru*> GetFru(absl::string_view key) const override;
+  absl::StatusOr<const FruTable*> GetAllFrus() const override;
+  void UpdateFruAndTopology(const RawFruTable& fru_table) override;
+  void SetSmartRouter(::crow::RouterInterface* smart_router) override;
+  nlohmann::json ToJson() const override;
+};
+
 }  // namespace milotic_tlbmc
 
 #endif  // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_CONFIG_ENTITY_CONFIG_H_
diff --git a/tlbmc/configs/entity_config_json_impl.cc b/tlbmc/configs/entity_config_json_impl.cc
index 712b31b..a2314ca 100644
--- a/tlbmc/configs/entity_config_json_impl.cc
+++ b/tlbmc/configs/entity_config_json_impl.cc
@@ -1269,7 +1269,7 @@
   for (const auto& [fru_key, _] : mutable_data.fru_table.key_to_fru()) {
     auto it = mutable_data.topology_config.mutable_fru_configs()->find(fru_key);
     if (it == mutable_data.topology_config.mutable_fru_configs()->end()) {
-      if (!GetTlbmcConfig().allow_dangling_frus) {
+      if (!GetTlbmcConfig().fru_collector_module().allow_dangling_frus()) {
         mutable_data.parsed_status = absl::InternalError(absl::StrCat(
             "Invalid topology: no config with matching probe found for FRU: ",
             fru_key));
@@ -2210,8 +2210,7 @@
     absl::string_view config_location) {
   std::vector<nlohmann::json> config_list;
   if (!std::filesystem::exists(config_location)) {
-    return absl::NotFoundError(
-        absl::StrCat("Config location not found: ", config_location));
+    return config_list;
   }
   for (const auto& entry :
        std::filesystem::directory_iterator(config_location)) {
diff --git a/tlbmc/meson.build b/tlbmc/meson.build
index 06d1539..aea8c93 100644
--- a/tlbmc/meson.build
+++ b/tlbmc/meson.build
@@ -4,6 +4,7 @@
 all_protos = [
 # go/keep-sorted start
   'ad_hoc_fru_config.proto',
+  'central_config.proto',
   'entity_common_config.proto',
   'fan_controller_config.proto',
   'fan_pwm_config.proto',
@@ -96,6 +97,7 @@
   'collector/fru_collector.cc',
   'collector/sensor_collector.cc',
   'configs/blocklist_parser.cc',
+  'configs/entity_config.cc',
   'configs/entity_config_json_impl.cc',
   'configs/expression.cc',
   'file/dir.cc',
diff --git a/tlbmc/redfish/app.h b/tlbmc/redfish/app.h
index b241071..50144df 100644
--- a/tlbmc/redfish/app.h
+++ b/tlbmc/redfish/app.h
@@ -46,8 +46,8 @@
 class RedfishApp : public ::crow::AppInterface {
  public:
   // The Redfish app for tlbmc only.
-  explicit RedfishApp(std::unique_ptr<milotic_tlbmc::Store> store);
-  explicit RedfishApp(std::unique_ptr<milotic_tlbmc::Pacemaker> pacemaker);
+  explicit RedfishApp(std::unique_ptr<Store> store);
+  explicit RedfishApp(std::unique_ptr<Pacemaker> pacemaker);
   RedfishApp() = default;
   RedfishApp(const RedfishApp&) = delete;
   RedfishApp(RedfishApp&&) = delete;
diff --git a/tlbmc/sensors/fan_controller.cc b/tlbmc/sensors/fan_controller.cc
index eeb08fc..6cb0be0 100644
--- a/tlbmc/sensors/fan_controller.cc
+++ b/tlbmc/sensors/fan_controller.cc
@@ -74,7 +74,9 @@
       I2cHwmonBasedSensor::CreateDeviceAndReturnsHwmonPath(
           i2c_common_config, driver_name, i2c_sysfs);
   if (!hwmon_path.ok()) {
-    if (!GetTlbmcConfig().allow_sensor_creation_failure) {
+    if (!GetTlbmcConfig()
+             .sensor_collector_module()
+             .allow_sensor_creation_failure()) {
       return hwmon_path.status();
     }
 
diff --git a/tlbmc/sensors/fan_pwm.cc b/tlbmc/sensors/fan_pwm.cc
index 45f5273..e6b19df 100644
--- a/tlbmc/sensors/fan_pwm.cc
+++ b/tlbmc/sensors/fan_pwm.cc
@@ -76,7 +76,9 @@
 
   auto it = fan_controller.GetIndexToPwms().find(config.index());
   if (it == fan_controller.GetIndexToPwms().end()) {
-    if (!GetTlbmcConfig().allow_sensor_creation_failure) {
+    if (!GetTlbmcConfig()
+             .sensor_collector_module()
+             .allow_sensor_creation_failure()) {
       return absl::InvalidArgumentError(absl::Substitute(
           "Fan controller $0 does not have a PWM file at index $1",
           absl::StrCat(fan_controller.GetI2cCommonConfig()), config.index()));
diff --git a/tlbmc/sensors/fan_tach.cc b/tlbmc/sensors/fan_tach.cc
index 13a314c..8aad9f2 100644
--- a/tlbmc/sensors/fan_tach.cc
+++ b/tlbmc/sensors/fan_tach.cc
@@ -54,7 +54,9 @@
 
   auto it = fan_controller.GetIndexToTachs().find(config.index());
   if (it == fan_controller.GetIndexToTachs().end()) {
-    if (!GetTlbmcConfig().allow_sensor_creation_failure) {
+    if (!GetTlbmcConfig()
+             .sensor_collector_module()
+             .allow_sensor_creation_failure()) {
       return absl::InvalidArgumentError(absl::Substitute(
           "Fan controller $0 does not have a tach file at index $1",
           absl::StrCat(fan_controller.GetI2cCommonConfig()), config.index()));
diff --git a/tlbmc/sensors/hwmon_temp_sensor.cc b/tlbmc/sensors/hwmon_temp_sensor.cc
index 1495186..79cd943 100644
--- a/tlbmc/sensors/hwmon_temp_sensor.cc
+++ b/tlbmc/sensors/hwmon_temp_sensor.cc
@@ -95,7 +95,8 @@
       I2cHwmonBasedSensor::CreateDeviceAndReturnsHwmonPath(
           i2c_common_config, driver_name, i2c_sysfs);
   if (!hwmon_path.ok()) {
-    if (!GetTlbmcConfig().allow_sensor_creation_failure) {
+    if (!GetTlbmcConfig().sensor_collector_module()
+             .allow_sensor_creation_failure()) {
       return hwmon_path.status();
     }
 
diff --git a/tlbmc/sensors/psu_sensor.cc b/tlbmc/sensors/psu_sensor.cc
index 626c87d..cac1517 100644
--- a/tlbmc/sensors/psu_sensor.cc
+++ b/tlbmc/sensors/psu_sensor.cc
@@ -334,7 +334,9 @@
       I2cHwmonBasedSensor::CreateDeviceAndReturnsHwmonPath(
           i2c_common_config, driver_name, i2c_sysfs);
   if (!hwmon_path.ok()) {
-    if (!GetTlbmcConfig().allow_sensor_creation_failure) {
+    if (!GetTlbmcConfig()
+             .sensor_collector_module()
+             .allow_sensor_creation_failure()) {
       return hwmon_path.status();
     }
 
diff --git a/tlbmc/store/store_impl.cc b/tlbmc/store/store_impl.cc
index a95daef..57c82ae 100644
--- a/tlbmc/store/store_impl.cc
+++ b/tlbmc/store/store_impl.cc
@@ -17,6 +17,7 @@
 #include "absl/time/time.h"
 #include "g3/macros.h"
 #include "nlohmann/json.hpp"
+#include "tlbmc/central_config/config.h"
 #include "tlbmc/collector/collector.h"
 #include "tlbmc/collector/fru_collector.h"
 #include "tlbmc/collector/sensor_collector.h"
@@ -45,7 +46,7 @@
                                             Collector::Type type) const {
   switch (type) {
     case Collector::Type::kSensor:
-        return all_collectors_.sensor_collector->ConfigureCollection(config);
+      return all_collectors_.sensor_collector->ConfigureCollection(config);
     default:
       return absl::InvalidArgumentError("Unsupported collector type");
   }
@@ -156,82 +157,92 @@
       .fru_scanners = options.fru_scanners,
       .ad_hoc_fru_scanning_configs = std::move(ad_hoc_fru_scanning_configs)};
 
-  absl::Time fru_scan_start_time = absl::Now();
-  Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Scan-FRUs-Begin",
-                                           fru_scan_start_time);
-
-  ECCLESIA_ASSIGN_OR_RETURN(
-      std::unique_ptr<FruCollector> fru_collector,
-      options.collector_factory->CreateFruCollector(fru_collector_options));
-
-  const RawFruTable fru_table = fru_collector->GetCopyOfCurrentScannedFrus();
-  absl::Time fru_scan_end_time = absl::Now();
-  metrics_at_bootup.fru_scan_and_collector_create_duration =
-      fru_scan_end_time - fru_scan_start_time;
-  Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Scan-FRUs-End",
-                                           fru_scan_end_time);
-
-  // Load Configs
-  absl::Time load_configs_start_time = absl::Now();
-  Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Load-Configs-Begin",
-                                           load_configs_start_time);
-
-  absl::StatusOr<std::unique_ptr<EntityConfig>> entity_config =
-      options.entity_config_reader->CreateEntityConfig(fru_table,
-                                                       ad_hoc_fru_count);
-  if (!entity_config.ok()) {
-    LOG(ERROR) << "Failed to create entity config: " << entity_config.status();
-    LOG(ERROR) << "The cached FRU table will be deleted.";
-    // If Store is bad, then cache may be bad too. Let cache reload on next
-    // boot.
-    std::filesystem::remove(fru_collector_options.cached_fru_table_path);
-    return entity_config.status();
-  }
-
-  absl::Time load_configs_end_time = absl::Now();
-  metrics_at_bootup.topology_config_load_duration =
-      load_configs_end_time - load_configs_start_time;
-  Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Load-Configs-End",
-                                           load_configs_end_time);
-
-  // EntityConfig must be shared as it is used by the FruCollector.
+  AllCollectors all_collectors = {
+      .sensor_collector = EmptySensorCollector::Create(),
+      .fru_collector = EmptyFruCollector::Create(),
+  };
   std::shared_ptr<EntityConfig> entity_config_shared =
-      absl::ShareUniquePtr(std::move(*entity_config));
+      EmptyEntityConfigImpl::Create();
+  if (GetTlbmcConfig().fru_collector_module().enabled()) {
+    absl::Time fru_scan_start_time = absl::Now();
+    Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Scan-FRUs-Begin",
+                                             fru_scan_start_time);
 
-  fru_collector->SetEntityConfig(entity_config_shared);
+    ECCLESIA_ASSIGN_OR_RETURN(
+        std::unique_ptr<FruCollector> fru_collector,
+        options.collector_factory->CreateFruCollector(fru_collector_options));
 
-  // Now create all the collectors one by one.
-  absl::Time sensor_collector_create_start_time = absl::Now();
-  Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Create-SensorCollector-Begin",
-                                           sensor_collector_create_start_time);
+    const RawFruTable fru_table = fru_collector->GetCopyOfCurrentScannedFrus();
+    absl::Time fru_scan_end_time = absl::Now();
+    metrics_at_bootup.fru_scan_and_collector_create_duration =
+        fru_scan_end_time - fru_scan_start_time;
+    Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Scan-FRUs-End",
+                                             fru_scan_end_time);
 
-  absl::StatusOr<std::unique_ptr<SensorCollector>> sensor_collector =
-      options.collector_factory->CreateSensorCollector(
-          {.entity_config = *entity_config_shared,
-           .i2c_sysfs = *options.i2c_sysfs,
-           .override_sensor_sampling_interval_ms =
-               options.override_sensor_sampling_interval_ms});
-  if (!sensor_collector.ok()) {
-    LOG(ERROR) << "Failed to create sensor collector: "
-               << sensor_collector.status();
-    LOG(ERROR) << "The cached FRU table will be deleted.";
-    // If Store is bad, then cache may be bad too. Let cache reload on next
-    // boot.
-    std::filesystem::remove(fru_collector_options.cached_fru_table_path);
-    return sensor_collector.status();
+    // Load Configs
+    absl::Time load_configs_start_time = absl::Now();
+    Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Load-Configs-Begin",
+                                             load_configs_start_time);
+
+    absl::StatusOr<std::unique_ptr<EntityConfig>> entity_config =
+        options.entity_config_reader->CreateEntityConfig(fru_table,
+                                                         ad_hoc_fru_count);
+    if (!entity_config.ok()) {
+      LOG(ERROR) << "Failed to create entity config: "
+                 << entity_config.status();
+      LOG(ERROR) << "The cached FRU table will be deleted.";
+      // If Store is bad, then cache may be bad too. Let cache reload on next
+      // boot.
+      std::filesystem::remove(fru_collector_options.cached_fru_table_path);
+      return entity_config.status();
+    }
+
+    absl::Time load_configs_end_time = absl::Now();
+    metrics_at_bootup.topology_config_load_duration =
+        load_configs_end_time - load_configs_start_time;
+    Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Load-Configs-End",
+                                             load_configs_end_time);
+
+    // EntityConfig must be shared as it is used by the FruCollector.
+    entity_config_shared = absl::ShareUniquePtr(std::move(*entity_config));
+
+    fru_collector->SetEntityConfig(entity_config_shared);
+    all_collectors.fru_collector = std::move(fru_collector);
+
+    if (GetTlbmcConfig().sensor_collector_module().enabled()) {
+      // Now create all the collectors one by one.
+      absl::Time sensor_collector_create_start_time = absl::Now();
+      Tracer::GetInstance().AddOneOffDatapoint(
+          "Tlbmc-Create-SensorCollector-Begin",
+          sensor_collector_create_start_time);
+
+      absl::StatusOr<std::unique_ptr<SensorCollector>> sensor_collector =
+          options.collector_factory->CreateSensorCollector(
+              {.entity_config = *entity_config_shared,
+               .i2c_sysfs = *options.i2c_sysfs,
+               .override_sensor_sampling_interval_ms =
+                   options.override_sensor_sampling_interval_ms});
+      if (!sensor_collector.ok()) {
+        LOG(ERROR) << "Failed to create sensor collector: "
+                   << sensor_collector.status();
+        LOG(ERROR) << "The cached FRU table will be deleted.";
+        // If Store is bad, then cache may be bad too. Let cache reload on next
+        // boot.
+        std::filesystem::remove(fru_collector_options.cached_fru_table_path);
+        return sensor_collector.status();
+      }
+
+      absl::Time sensor_collector_create_end_time = absl::Now();
+      metrics_at_bootup.sensor_collector_create_duration =
+          sensor_collector_create_end_time - sensor_collector_create_start_time;
+      Tracer::GetInstance().AddOneOffDatapoint(
+          "Tlbmc-Create-SensorCollector-End", sensor_collector_create_end_time);
+      all_collectors.sensor_collector = std::move(*sensor_collector);
+    }
   }
+  // Add support for other collectors here
 
-  absl::Time sensor_collector_create_end_time = absl::Now();
-  metrics_at_bootup.sensor_collector_create_duration =
-      sensor_collector_create_end_time - sensor_collector_create_start_time;
-  Tracer::GetInstance().AddOneOffDatapoint("Tlbmc-Create-SensorCollector-End",
-                                           sensor_collector_create_end_time);
-
-  // TODO(rahulkpr): Add support for other collectors.
-
-  AllCollectors all_collectors;
-  all_collectors.sensor_collector = std::move(*sensor_collector);
-  all_collectors.fru_collector = std::move(fru_collector);
+  // Create the store
   int periodic_dump_interval_ms =
       options.override_store_snapshot_interval_ms.value_or(
           kDefaultPeriodicDumpIntervalMs);
@@ -264,6 +275,11 @@
         LOG(WARNING) << "=== Collector Scheduler stats ===";
         LOG(WARNING) << GetSchedulerStats().dump();
         LOG(WARNING) << "=== END ===";
+
+        // Dump the central config.
+        LOG(WARNING) << "=== Central Config ===";
+        LOG(WARNING) << GetTlbmcConfig();
+        LOG(WARNING) << "=== END ===";
         done();
       },
       store_snapshot_interval);