Add application to monitor filesystem utilization.

Change-Id: I2305ac657c7b2c528d31e27f44b1a7d5015c2492
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/aos/util/BUILD b/aos/util/BUILD
index 7bb4061..599d477 100644
--- a/aos/util/BUILD
+++ b/aos/util/BUILD
@@ -578,3 +578,25 @@
         "//aos/testing:path",
     ],
 )
+
+static_flatbuffer(
+    name = "filesystem_fbs",
+    srcs = ["filesystem.fbs"],
+)
+
+cc_static_flatbuffer(
+    name = "filesystem_schema",
+    function = "aos::util::FilesystemStatusSchema",
+    target = ":filesystem_fbs_reflection_out",
+)
+
+cc_binary(
+    name = "filesystem_monitor",
+    srcs = ["filesystem_monitor.cc"],
+    deps = [
+        ":filesystem_fbs",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "@com_google_absl//absl/strings",
+    ],
+)
diff --git a/aos/util/filesystem.fbs b/aos/util/filesystem.fbs
new file mode 100644
index 0000000..0571d15
--- /dev/null
+++ b/aos/util/filesystem.fbs
@@ -0,0 +1,23 @@
+namespace aos.util;
+
+table Filesystem {
+  // Mountpoint of the filesystem in question.
+  path: string (id: 0);
+  // Type (e.g., "ext4") of the filesystem.
+  type: string (id: 1);
+  // Total size of the filesystem, in bytes.
+  overall_space: uint64 (id: 2);
+  // Total free space on the filesystem, in bytes.
+  free_space: uint64 (id: 3);
+  // Total number of inodes on this filesystem.
+  overall_inodes: uint64 (id: 4);
+  // Total free inodes on this filesystem.
+  free_inodes: uint64 (id: 5);
+}
+
+// Table to track the current state of a compute platform's filesystem.
+table FilesystemStatus {
+  filesystems: [Filesystem] (id: 0);
+}
+
+root_type FilesystemStatus;
diff --git a/aos/util/filesystem_monitor.cc b/aos/util/filesystem_monitor.cc
new file mode 100644
index 0000000..4efb141
--- /dev/null
+++ b/aos/util/filesystem_monitor.cc
@@ -0,0 +1,140 @@
+#include <sys/statvfs.h>
+
+#include "absl/strings/str_split.h"
+#include "gflags/gflags.h"
+
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "aos/util/filesystem_generated.h"
+
+DEFINE_string(config, "aos_config.json", "File path of aos configuration");
+
+namespace aos::util {
+namespace {
+std::optional<std::string> ReadShortFile(std::string_view file_name) {
+  // Open as input and seek to end immediately.
+  std::ifstream file(std::string(file_name), std::ios_base::in);
+  if (!file.good()) {
+    VLOG(1) << "Can't read " << file_name;
+    return std::nullopt;
+  }
+  const size_t kMaxLineLength = 4096;
+  char buffer[kMaxLineLength];
+  file.read(buffer, kMaxLineLength);
+  if (!file.eof()) {
+    return std::nullopt;
+  }
+  return std::string(buffer, file.gcount());
+}
+}  // namespace
+
+// Periodically sends out the Filesystems message with filesystem utilization
+// info.
+class FilesystemMonitor {
+ public:
+  FilesystemMonitor(aos::EventLoop *event_loop)
+      : event_loop_(event_loop),
+        sender_(event_loop_->MakeSender<FilesystemStatus>("/aos")) {
+    periodic_timer_ =
+        event_loop_->AddTimer([this]() { PublishFilesystemStatus(); });
+    event_loop_->OnRun([this]() {
+      periodic_timer_->Schedule(event_loop_->monotonic_now(),
+                                std::chrono::seconds(5));
+    });
+  }
+
+ private:
+  void PublishFilesystemStatus() {
+    aos::Sender<FilesystemStatus>::Builder builder = sender_.MakeBuilder();
+
+    std::optional<std::string> contents = ReadShortFile("/proc/self/mountinfo");
+
+    CHECK(contents.has_value());
+
+    std::vector<flatbuffers::Offset<Filesystem>> filesystems;
+
+    // Iterate through /proc/self/mounts to find all the filesystems.
+    for (std::string_view line :
+         absl::StrSplit(std::string_view(contents->c_str(), contents->size()),
+                        '\n', absl::SkipWhitespace())) {
+      // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt for
+      // the format.
+      std::vector<std::string_view> elements =
+          absl::StrSplit(line, ' ', absl::SkipWhitespace());
+
+      // First thing after - is the filesystem type.
+      size_t i = 6;
+      while (elements[i] != "-") {
+        ++i;
+        CHECK_LT(i + 1, elements.size());
+      }
+
+      // Mount point is the 4th element.
+      std::string mount_point(elements[4]);
+      std::string_view type = elements[i + 1];
+
+      // Ignore filesystems without reasonable types.
+      if (type != "ext2" && type != "xfs" && type != "vfat" && type != "ext3" &&
+          type != "ext4" && type != "tmpfs" && type != "devtmpfs") {
+        continue;
+      }
+      VLOG(1) << mount_point << ", type " << type;
+
+      struct statvfs info;
+
+      PCHECK(statvfs(mount_point.c_str(), &info) == 0);
+
+      VLOG(1) << "overall size: " << info.f_frsize * info.f_blocks << ", free "
+              << info.f_bfree * info.f_bsize << ", inodes " << info.f_files
+              << ", free " << info.f_ffree;
+
+      flatbuffers::Offset<flatbuffers::String> path_offset =
+          builder.fbb()->CreateString(mount_point);
+      flatbuffers::Offset<flatbuffers::String> type_offset =
+          builder.fbb()->CreateString(type);
+      Filesystem::Builder filesystem_builder =
+          builder.MakeBuilder<Filesystem>();
+      filesystem_builder.add_path(path_offset);
+      filesystem_builder.add_type(type_offset);
+      filesystem_builder.add_overall_space(info.f_frsize * info.f_blocks);
+      filesystem_builder.add_free_space(info.f_bfree * info.f_bsize);
+      filesystem_builder.add_overall_inodes(info.f_files);
+      filesystem_builder.add_free_inodes(info.f_ffree);
+
+      filesystems.emplace_back(filesystem_builder.Finish());
+    }
+
+    flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Filesystem>>>
+        filesystems_offset = builder.fbb()->CreateVector(filesystems);
+
+    FilesystemStatus::Builder filesystem_status_builder =
+        builder.MakeBuilder<FilesystemStatus>();
+
+    filesystem_status_builder.add_filesystems(filesystems_offset);
+
+    (void)builder.Send(filesystem_status_builder.Finish());
+  }
+
+  aos::EventLoop *event_loop_;
+
+  aos::Sender<FilesystemStatus> sender_;
+
+  aos::TimerHandler *periodic_timer_;
+};
+
+}  // namespace aos::util
+
+int main(int argc, char **argv) {
+  aos::InitGoogle(&argc, &argv);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(FLAGS_config);
+
+  aos::ShmEventLoop shm_event_loop(&config.message());
+
+  aos::util::FilesystemMonitor filesystem_monitor(&shm_event_loop);
+
+  shm_event_loop.Run();
+
+  return 0;
+}
diff --git a/y2024/BUILD b/y2024/BUILD
index 212f70e..e3fbb72 100644
--- a/y2024/BUILD
+++ b/y2024/BUILD
@@ -124,6 +124,7 @@
         "//y2024/localizer:status_fbs",
         "//y2024/localizer:visualization_fbs",
         "//aos/network:timestamp_fbs",
+        "//aos/util:filesystem_fbs",
         "//aos/network:remote_message_fbs",
         "//frc971/vision:calibration_fbs",
         "//frc971/vision:target_map_fbs",
@@ -183,6 +184,7 @@
         "//y2024/localizer:visualization_fbs",
         "//frc971/vision:target_map_fbs",
         "//frc971/vision:vision_fbs",
+        "//aos/util:filesystem_fbs",
         "@com_github_foxglove_schemas//:schemas",
     ],
     target_compatible_with = ["@platforms//os:linux"],
diff --git a/y2024/y2024_imu.json b/y2024/y2024_imu.json
index 3da3e11..5990242 100644
--- a/y2024/y2024_imu.json
+++ b/y2024/y2024_imu.json
@@ -2,6 +2,12 @@
   "channels": [
     {
       "name": "/imu/aos",
+      "type": "aos.util.FilesystemStatus",
+      "source_node": "imu",
+      "frequency": 2
+    },
+    {
+      "name": "/imu/aos",
       "type": "aos.JoystickState",
       "source_node": "imu",
       "frequency": 100,
diff --git a/y2024/y2024_orin1.json b/y2024/y2024_orin1.json
index 099a06a..01dae9e 100644
--- a/y2024/y2024_orin1.json
+++ b/y2024/y2024_orin1.json
@@ -2,6 +2,12 @@
   "channels": [
     {
       "name": "/orin1/aos",
+      "type": "aos.util.FilesystemStatus",
+      "source_node": "orin1",
+      "frequency": 2
+    },
+    {
+      "name": "/orin1/aos",
       "type": "aos.timing.Report",
       "source_node": "orin1",
       "frequency": 50,