text: /**
   * Copyright 2004-present, Facebook, Inc.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  #include "ProcFs.h"
  
  #include <sys/types.h>
  #include <unistd.h>
  #include <algorithm>
  #include <array>
  #include <climits>
  #include <stdexcept>
  
  namespace facebook {
  namespace profilo {
  namespace counters {
  
  static constexpr int kMaxProcFileLength = 64;
  
  TaskStatInfo getStatInfo(int32_t tid) {
    return TaskStatFile(tid).refresh();
  }
  
  ThreadStatInfo ThreadStatInfo::createThreadStatInfo(int32_t tid) {
    return ThreadStatInfo{
        .cpuTimeMs = TraceCounter(QuickLogConstants::THREAD_CPU_TIME, tid),
        .state = TraceCounter(QuickLogConstants::THREAD_STATE, tid),
        .majorFaults =
            TraceCounter(QuickLogConstants::QL_THREAD_FAULTS_MAJOR, tid),
        .cpuNum = TraceCounter(QuickLogConstants::THREAD_CPU_NUM, tid),
        .kernelCpuTimeMs =
            TraceCounter(QuickLogConstants::THREAD_KERNEL_CPU_TIME, tid),
        .minorFaults =
            TraceCounter(QuickLogConstants::THREAD_SW_FAULTS_MINOR, tid),
        .threadPriority = TraceCounter(QuickLogConstants::THREAD_PRIORITY, tid),
        .highPrecisionCpuTimeMs =
            TraceCounter(QuickLogConstants::THREAD_CPU_TIME, tid),
        .waitToRunTimeMs =
            TraceCounter(QuickLogConstants::THREAD_WAIT_IN_RUNQUEUE_TIME, tid),
        .nrVoluntarySwitches =
            TraceCounter(QuickLogConstants::CONTEXT_SWITCHES_VOLUNTARY, tid),
        .nrInvoluntarySwitches =
            TraceCounter(QuickLogConstants::CONTEXT_SWITCHES_INVOLUNTARY, tid),
        .iowaitSum = TraceCounter(QuickLogConstants::IOWAIT_TIME, tid),
        .iowaitCount = TraceCounter(QuickLogConstants::IOWAIT_COUNT, tid),
        .availableStatsMask = 0,
    };
  }
  
  SchedstatInfo::SchedstatInfo() : cpuTimeMs(0), waitToRunTimeMs(0) {}
  
  SchedInfo::SchedInfo()
      : nrVoluntarySwitches(0),
        nrInvoluntarySwitches(0),
        iowaitSum(0),
        iowaitCount(0) {}
  
  TaskStatInfo::TaskStatInfo()
      : cpuTime(0),
        state(ThreadState::TS_UNKNOWN),
        majorFaults(0),
        cpuNum(-1),
        kernelCpuTimeMs(0),
        minorFaults(0),
        threadPriority(999) {}
  
  VmStatInfo::VmStatInfo()
      : nrFreePages(0),
        nrDirty(0),
        nrWriteback(0),
        pgPgIn(0),
        pgPgOut(0),
        pgMajFault(0),
        allocStall(0),
        pageOutrun(0),
        kswapdSteal(0) {}
  
  namespace {
  
  enum StatFileType : int8_t {
    STAT = 0,
    SCHEDSTAT = 1,
    SCHED = 1 << 1,
  };
  
  static const std::array<int32_t, 3> kFileStats = {{
      /*STAT*/ StatType::CPU_TIME | StatType::STATE | StatType::MAJOR_FAULTS |
          StatType::CPU_NUM | StatType::KERNEL_CPU_TIME | StatType::MINOR_FAULTS |
          StatType::THREAD_PRIORITY,
      /*SCHEDSTAT*/ StatType::HIGH_PRECISION_CPU_TIME |
          StatType::WAIT_TO_RUN_TIME,
      /*SCHED*/ StatType::NR_VOLUNTARY_SWITCHES |
          StatType::NR_INVOLUNTARY_SWITCHES | StatType::IOWAIT_SUM |
          StatType::IOWAIT_COUNT,
  }};
  
  inline ThreadState convertCharToStateEnum(char stateChar) {
    switch (stateChar) {
      case 'R':
        return TS_RUNNING;
      case 'S':
        return TS_SLEEPING;
      case 'D':
        return TS_WAITING;
      case 'Z':
        return TS_ZOMBIE;
      case 'T':
        return TS_STOPPED;
      case 't':
        return TS_TRACING_STOP;
      case 'X':
      case 'x':
        return TS_DEAD;
      case 'K':
        return TS_WAKEKILL;
      case 'W':
        return TS_WAKING;
      case 'P':
        return TS_PARKED;
    }
    return TS_UNKNOWN;
  }
  
  // Consumes data until `ch` or we reach `end`.
  // Returns a pointer immediately after `ch`.
  //
  // Throws std::runtime_error if `end` is reached before `ch`
  // or \0 is encountered anywhere in the string.
  char* skipUntil(char* data, const char* end, char ch) {
    // It's important that we check against `end`
    // before we dereference `data`.
    while (data < end && *data != ch) {
      if (*data == '\0') {
        throw std::runtime_error("Unexpected end of string");
      }
  
      ++data;
    }
  
    if (data == end) {
      throw std::runtime_error("Unexpected end of string");
    }
  
    // One past the `ch` character.
    return ++data;
  }
  
  TaskStatInfo parseStatFile(char* data, size_t size, uint32_t stats_mask) {
    const char* end = (data + size);
  
    data = skipUntil(data, end, ' '); // pid
    data = skipUntil(data, end, ')'); // name
    data = skipUntil(data, end, ' '); // space after name
    char state = *data; // state
  
    data = skipUntil(data, end, ' '); // state
  
    data = skipUntil(data, end, ' '); // ppid
    data = skipUntil(data, end, ' '); // pgrp
    data = skipUntil(data, end, ' '); // session
    data = skipUntil(data, end, ' '); // tty_nr
    data = skipUntil(data, end, ' '); // tpgid
  
    data = skipUntil(data, end, ' '); // flags
  
    char* endptr = nullptr;
    auto minflt = parse_ull(data, &endptr); // minflt
    if (errno == ERANGE || data == endptr || endptr > end) {
      throw std::runtime_error("Could not parse minflt");
    }
    data = skipUntil(endptr, end, ' ');
  
    data = skipUntil(data, end, ' '); // cminflt
  
    endptr = nullptr;
    auto majflt = parse_ull(data, &endptr); // majflt
    if (errno == ERANGE || data == endptr || endptr > end) {
      throw std::runtime_error("Could not parse majflt");
    }
    data = skipUntil(endptr, end, ' ');
  
    data = skipUntil(data, end, ' '); // cmajflt
  
    endptr = nullptr;
    auto utime = parse_ull(data, &endptr); // utime
    if (errno == ERANGE || data == endptr || endptr > end) {
      throw std::runtime_error("Could not parse utime");
    }
    data = skipUntil(endptr, end, ' ');
  
    endptr = nullptr;
    auto stime = parse_ull(data, &endptr); // stime
    if (errno == ERANGE || data == endptr || endptr > end) {
      throw std::runtime_error("Could not parse stime");
    }
    data = skipUntil(endptr, end, ' ');
  
    data = skipUntil(data, end, ' '); // cutime
    data = skipUntil(data, end, ' '); // cstime
  
    endptr = nullptr;
    auto priority = strtol(data, &endptr, 10); // priority
    if (errno == ERANGE || data == endptr || endptr > end) {
      throw std::runtime_error("Could not parse priority");
    }
  
    int cpuNum = 0;
    if (StatType::CPU_NUM & stats_mask) {
      data = skipUntil(endptr, end, ' ');
  
      data = skipUntil(data, end, ' '); // nice
      data = skipUntil(data, end, ' '); // num_threads
      data = skipUntil(data, end, ' '); // itrealvalue
      data = skipUntil(data, end, ' '); // starttime
  
      data = skipUntil(data, end, ' '); // vsize
      data = skipUntil(data, end, ' '); // rss
      data = skipUntil(data, end, ' '); // rsslim
      data = skipUntil(data, end, ' '); // startcode
      data = skipUntil(data, end, ' '); // endcode
      data = skipUntil(data, end, ' '); // startstack
      data = skipUntil(data, end, ' '); // kstkesp
      data = skipUntil(data, end, ' '); // kstkeip
  
      data = skipUntil(data, end, ' '); // signal
      data = skipUntil(data, end, ' '); // blocked
      data = skipUntil(data, end, ' '); // sigignore
      data = skipUntil(data, end, ' '); // sigcatch
      data = skipUntil(data, end, ' '); // wchan
      data = skipUntil(data, end, ' '); // nswap
      data = skipUntil(data, end, ' '); // cnswap
      data = skipUntil(data, end, ' '); // exit_signal
      endptr = nullptr;
      cpuNum = strtol(data, &endptr, 10); // processor
      if (errno == ERANGE || data == endptr || endptr > end) {
        throw std::runtime_error("Could not parse cpu num");
      }
    }
  
    // SYSTEM_CLK_TCK is defined as 100 in linux as is unchanged in android.
    // Therefore there are 10 milli seconds in each clock tick.
    static int kClockTicksMs = systemClockTickIntervalMs();
  
    TaskStatInfo info{};
    info.cpuTime = kClockTicksMs * (utime + stime);
    info.kernelCpuTimeMs = kClockTicksMs * stime;
    info.state = convertCharToStateEnum(state);
    info.majorFaults = majflt;
    info.minorFaults = minflt;
    info.cpuNum = cpuNum;
    info.threadPriority = priority;
  
    return info;
  }
  
  std::string tidToStatPath(int32_t tid, const char* stat_name) {
    char threadStatPath[kMaxProcFileLength]{};
  
    int bytesWritten = snprintf(
        threadStatPath,
        kMaxProcFileLength,
        "/proc/self/task/%d/%s",
        tid,
        stat_name);
  
    if (bytesWritten < 0 || bytesWritten >= kMaxProcFileLength) {
      throw std::system_error(
          errno, std::system_category(), "Could not format file path");
    }
    return std::string(threadStatPath);
  }
  
  SchedstatInfo parseSchedstatFile(char* data, size_t size) {
    const char* end = (data + size);
  
    char* endptr = nullptr;
    auto run_time_ns = parse_ull(data, &endptr); // run time
    if (errno == ERANGE || data == endptr || endptr > end) {
      throw std::runtime_error("Could not parse run time");
    }
    data = skipUntil(endptr, end, ' ');
    endptr = nullptr;
    auto wait_time_ns = parse_ull(data, &endptr); // run time
    if (errno == ERANGE || data == endptr || endptr > end) {
      throw std::runtime_error("Could not parse wait time");
    }
  
    SchedstatInfo info{};
    info.cpuTimeMs = run_time_ns / 1000000;
    info.waitToRunTimeMs = wait_time_ns / 1000000;
  
    return info;
  }
  
  StatmInfo parseStatmFile(char* data, size_t size) {
    const char* end = (data + size);
    data = skipUntil(data, end, ' '); // size
    char* endptr = nullptr;
    auto resident = parse_ull(data, &endptr); // resident
    if (errno == ERANGE || data == endptr || endptr > end) {
      throw std::runtime_error("Could not parse resident");
    }
    data = skipUntil(endptr, end, ' ');
    endptr = nullptr;
    auto shared = parse_ull(data, &endptr); // shared
    if (errno == ERANGE || data == endptr || endptr > end) {
      throw std::runtime_error("Could not parse stime");
    }
    return (struct StatmInfo){.resident = resident, .shared = shared};
  }
  
  } // namespace
  
  TaskStatFile::TaskStatFile(int32_t tid)
      : BaseStatFile<TaskStatInfo>(tidToStatPath(tid, "stat")) {}
  
  TaskStatInfo TaskStatFile::doRead(int fd, uint32_t requested_stats_mask) {
    // This is a conservative upper bound, so we can read the
    // entire file in one fread call.
    constexpr size_t kMaxStatFileLength = 512;
  
    char buffer[kMaxStatFileLength]{};
    int bytes_read = read(fd, buffer, (sizeof(buffer) - 1));
    if (bytes_read < 0) {
      throw std::system_error(
          errno, std::system_category(), "Could not read stat file");
    }
  
    // At this point we know that `buffer` must be null terminated because we
    // zeroed the array before we read at most `sizeof(buffer) - 1` from the file.
    return parseStatFile(buffer, bytes_read, requested_stats_mask);
  }
  
  TaskSchedstatFile::TaskSchedstatFile(int32_t tid)
      : BaseStatFile<SchedstatInfo>(tidToStatPath(tid, "schedstat")) {}
  
  SchedstatInfo TaskSchedstatFile::doRead(int fd, uint32_t requested_stats_mask) {
    // This is a conservative upper bound, so we can read the
    // entire file in one fread call.
    constexpr size_t kMaxStatFileLength = 128;
  
    char buffer[kMaxStatFileLength]{};
    int bytes_read = read(fd, buffer, (sizeof(buffer) - 1));
    if (bytes_read < 0) {
      throw std::system_error(
          errno, std::system_category(), "Could not read schedstat file");
    }
  
    // At this point we know that `buffer` must be null terminated because we
    // zeroed the array before we read at most `sizeof(buffer) - 1` from the file.
    return parseSchedstatFile(buffer, bytes_read);
  }
  
  TaskSchedFile::TaskSchedFile(int32_t tid)
      : BaseStatFile<SchedInfo>(tidToStatPath(tid, "sched")),
        value_offsets_(),
        initialized_(false),
        value_size_(),
        buffer_(),
        availableStatsMask(0) {}
  
  SchedInfo TaskSchedFile::doRead(int fd, uint32_t requested_stats_mask) {
    int size = read(fd, buffer_, kMaxStatFileLength - 1);
    if (size < 0) {
      throw std::system_error(
          errno, std::system_category(), "Could not read stat file");
    }
    char* endfile = buffer_ + size;
  
    if (!initialized_) {
      struct KnownKey {
        const char* key;
        StatType type;
      };
  
      static std::array<KnownKey, 4> kKnownKeys = {
          {{"nr_voluntary_switches", StatType::NR_VOLUNTARY_SWITCHES},
           {"nr_involuntary_switches", StatType::NR_INVOLUNTARY_SWITCHES},
           {"se.statistics.iowait_count", StatType::IOWAIT_COUNT},
           {"se.statistics.iowait_sum", StatType::IOWAIT_SUM}}};
  
      // Skip 2 lines.
      auto endline = std::strchr(buffer_, '\n');
      if (endline == nullptr) {
        throw std::runtime_error("Unexpected file format");
      }
      endline = std::strchr(endline + 1, '\n');
      if (endline == nullptr) {
        throw std::runtime_error("Unexpected file format");
      }
  
      // The stat file line is in the form of key:value record with fixed line
      // length per metric, but which can vary depending on a metric name.
      // The key is aligned to the left and the value is aligned to the
      // right:  "key     :     value"
      // In the loop we parse the buffer line by line reading the key-value pairs.
      // If key is in the known keys (kKnownKeys) we calculate a global offset to
      // the value in the file and record it for fast access in the future.
      for (auto pos = endline + 1; pos < endfile;) {
        // Sometimes colon delimiter can go right after key ("key:") and we should
        // account for this case too.
        auto key_end = std::strchr(pos, ' ');
        auto delim = std::strchr(pos, ':');
        if (key_end == nullptr || delim == nullptr) {
          break;
        }
        auto key_len =
            std::min(std::distance(pos, key_end), std::distance(pos, delim));
  
        auto known_key = std::find_if(
            kKnownKeys.begin(),
            kKnownKeys.end(),
            [pos, key_len](KnownKey const& key) {
              return std::strncmp(key.key, pos, key_len) == 0;
            });
        if (known_key != kKnownKeys.end()) {
          int value_offset = std::distance(buffer_, delim) + 1;
          value_offsets_.push_back(std::make_pair(known_key->type, value_offset));
          availableStatsMask |= known_key->type;
        }
        // Switch to the next line.
        pos = std::strchr(delim, '\n');
        if (!pos) {
          break;
        }
        if (!value_size_) {
          // Saving allocated space for value (fixed size for all stats) to detect
          // truncated values.
          value_size_ = std::distance(delim, pos);
        }
        ++pos;
      }
  
      initialized_ = true;
    }
  
    if (value_offsets_.empty()) {
      throw std::runtime_error("No target fields found");
    }
  
    SchedInfo schedInfo{};
    for (auto& entry : value_offsets_) {
      auto key_type = entry.first;
      auto value_offset = entry.second;
  
      if (value_offset + value_size_ > size) {
        // Possibly truncated value, ignoring.
        continue;
      }
      errno = 0;
      char* endptr;
      auto value = parse_ull(buffer_ + value_offset, &endptr);
      if (errno == ERANGE || (buffer_ + value_offset) == endptr ||
          endptr > endfile) {
        throw std::runtime_error("Could not parse value");
      }
  
      switch (key_type) {
        case StatType::NR_VOLUNTARY_SWITCHES:
          schedInfo.nrVoluntarySwitches = value;
          break;
        case StatType::NR_INVOLUNTARY_SWITCHES:
          schedInfo.nrInvoluntarySwitches = value;
          break;
        case StatType::IOWAIT_COUNT:
          schedInfo.iowaitCount = value;
          break;
        case StatType::IOWAIT_SUM:
          schedInfo.iowaitSum = value;
          break;
      }
    }
  
    return schedInfo;
  }
  
  VmStatFile::VmStatFile(std::string path)
      : OrderedKeyedStatFile(
            path,
  
            // The order corresponds to the order in /proc/vmstat generated by the
            // Linux kernel
            {{"nr_free_pages",
              sizeof("nr_free_pages") - 1,
              kNotSet,
              &VmStatInfo::nrFreePages},
             {"nr_dirty", sizeof("nr_dirty") - 1, kNotSet, &VmStatInfo::nrDirty},
             {"nr_writeback",
              sizeof("nr_writeback") - 1,
              kNotSet,
              &VmStatInfo::nrWriteback},
             {"pgpgin", sizeof("pgpgin") - 1, kNotSet, &VmStatInfo::pgPgIn},
             {"pgpgout", sizeof("pgpgout") - 1, kNotSet, &VmStatInfo::pgPgOut},
             {"pgmajfault",
              sizeof("pgmajfault") - 1,
              kNotSet,
              &VmStatInfo::pgMajFault},
             // On latest kernel versions "kswapd_steal" was split by zones and
             // became: "pgsteal_kswapd_dma" + "pgsteal_kswapd_normal" +
             // "pgsteal_kswapd_movable"
             {"pgsteal_kswapd_dma",
              sizeof("pgsteal_kswapd_dma") - 1,
              kNotSet,
              &VmStatInfo::kswapdSteal},
             {"pgsteal_kswapd_normal",
              sizeof("pgsteal_kswapd_normal") - 1,
              kNotSet,
              &VmStatInfo::kswapdSteal},
             {"pgsteal_kswapd_movable",
              sizeof("pgsteal_kswapd_movable") - 1,
              kNotSet,
              &VmStatInfo::kswapdSteal},
             {"kswapd_steal",
              sizeof("kswapd_steal") - 1,
              kNotSet,
              &VmStatInfo::kswapdSteal},
             {"pageoutrun",
              sizeof("pageoutrun") - 1,
              kNotSet,
              &VmStatInfo::pageOutrun},
             {"allocstall",
              sizeof("allocstall") - 1,
              kNotSet,
              &VmStatInfo::allocStall}}) {}
  
  VmStatFile::VmStatFile() : VmStatFile("/proc/vmstat") {}
  
  MeminfoFile::MeminfoFile(std::string path)
      : OrderedKeyedStatFile(
            path,
  
            // The order corresponds to the order in /proc/meminfo generated by
            // the Linux kernel
            {
                {"MemFree:",
                 sizeof("MemFree:") - 1,
                 kNotSet,
                 &MeminfoInfo::freeKB},
                {"Cached:",
                 sizeof("Cached:") - 1,
                 kNotSet,
                 &MeminfoInfo::cachedKB},
                {"Active:",
                 sizeof("Active:") - 1,
                 kNotSet,
                 &MeminfoInfo::activeKB},
                {"Inactive:",
                 sizeof("Inactive:") - 1,
                 kNotSet,
                 &MeminfoInfo::inactiveKB},
                {"Dirty:", sizeof("Dirty:") - 1, kNotSet, &MeminfoInfo::dirtyKB},
                {"Writeback:",
                 sizeof("Writeback:") - 1,
                 kNotSet,
                 &MeminfoInfo::writebackKB},
            }) {}
  
  MeminfoFile::MeminfoFile() : MeminfoFile("/proc/meminfo") {}
  
  StatmInfo ProcStatmFile::doRead(int fd, uint32_t requested_stats_mask) {
    // This is a conservative upper bound, so we can read the
    // entire file in one fread call.
    constexpr size_t kMaxStatFileLength = 64;
  
    char buffer[kMaxStatFileLength]{};
    int bytes_read = read(fd, buffer, (sizeof(buffer) - 1));
    if (bytes_read < 0) {
      throw std::system_error(
          errno, std::system_category(), "Could not read statm file");
    }
  
    // At this point we know that `buffer` must be null terminated because we
    // zeroed the array before we read at most `sizeof(buffer) - 1` from the file.
    return parseStatmFile(buffer, bytes_read);
  }
  
  ThreadStatHolder::ThreadStatHolder(int32_t tid)
      : stat_file_(),
        schedstat_file_(),
        sched_file_(),
        last_info_(ThreadStatInfo::createThreadStatInfo(tid)),
        availableStatFilesMask_(0xff),
        availableStatsMask_(0),
        tid_(tid) {}
  
  void ThreadStatHolder::sampleAndLog(
      uint32_t requested_stats_mask,
      int32_t tid) {
    int64_t timestamp = monotonicTime();
    // If /proc/self/<tid>/schedstat is requested, we will try to read it.
    // If we get exception on first read the availableStatFilesMask will be
    // updated respectively. The second time this stat file will be ignored.
    if ((availableStatFilesMask_ & StatFileType::SCHEDSTAT) &&
        (kFileStats[StatFileType::SCHEDSTAT] & requested_stats_mask)) {
      if (schedstat_file_.get() == nullptr) {
        schedstat_file_ = std::make_unique<TaskSchedstatFile>(tid_);
      }
      try {
        auto schedstatInfo = schedstat_file_->refresh(requested_stats_mask);
        last_info_.waitToRunTimeMs.record(
            schedstatInfo.waitToRunTimeMs, timestamp);
        last_info_.highPrecisionCpuTimeMs.record(
            schedstatInfo.cpuTimeMs, timestamp);
        availableStatsMask_ |= kFileStats[StatFileType::SCHEDSTAT];
      } catch (const std::system_error& e) {
        // If 'schedstat' file is absent do not attempt the second time
        availableStatFilesMask_ ^= StatFileType::SCHEDSTAT;
        schedstat_file_.reset(nullptr);
      }
    }
    // Assuming that /proc/self/<tid>/stat is always available.
    if (kFileStats[StatFileType::STAT] & requested_stats_mask) {
      if (stat_file_.get() == nullptr) {
        stat_file_ = std::make_unique<TaskStatFile>(tid_);
      }
      auto statInfo = stat_file_->refresh(requested_stats_mask);
      if (!(availableStatsMask_ & StatType::HIGH_PRECISION_CPU_TIME)) {
        last_info_.cpuTimeMs.record(statInfo.cpuTime, timestamp);
      }
      last_info_.state.record(statInfo.state, timestamp);
      last_info_.cpuNum.record(statInfo.cpuNum, timestamp);
      last_info_.kernelCpuTimeMs.record(statInfo.kernelCpuTimeMs, timestamp);
      last_info_.majorFaults.record(statInfo.majorFaults, timestamp);
      last_info_.minorFaults.record(statInfo.minorFaults, timestamp);
      last_info_.threadPriority.record(statInfo.threadPriority, timestamp);
    }
    // If /proc/self/<tid>/sched is requested, we will try to read it.
    // If we get exception on first read the availableStatFilesMask will be
    // updated respectively. The second time this stat file will be ignored.
    if ((availableStatFilesMask_ & StatFileType::SCHED) &&
        (kFileStats[StatFileType::SCHED] & requested_stats_mask)) {
      if (sched_file_.get() == nullptr) {
        sched_file_ = std::make_unique<TaskSchedFile>(tid_);
      }
      try {
        auto schedInfo = sched_file_->refresh(requested_stats_mask);
        last_info_.nrVoluntarySwitches.record(
            schedInfo.nrVoluntarySwitches, timestamp);
        last_info_.nrInvoluntarySwitches.record(
            schedInfo.nrInvoluntarySwitches, timestamp);
        last_info_.iowaitSum.record(schedInfo.iowaitSum, timestamp);
        last_info_.iowaitCount.record(schedInfo.iowaitCount, timestamp);
        availableStatsMask_ |= sched_file_->availableStatsMask;
      } catch (const std::exception& e) {
        // If 'schedstat' file is absent do not attempt the second time
        availableStatFilesMask_ ^= StatFileType::SCHED;
        schedstat_file_.reset(nullptr);
      }
    }
    last_info_.availableStatsMask = availableStatsMask_;
  }
  
  ThreadStatInfo& ThreadStatHolder::getInfo() {
    return last_info_;
  }
  
  void ThreadCache::sampleAndLogForEach(
      uint32_t requested_stats_mask,
      const std::unordered_set<int32_t>* black_list) {
    try {
      const auto& threads = threadListFromProcFs();
  
      // Delete cached data for gone threads.
      for (auto iter = cache_.begin(); iter != cache_.end();) {
        if (threads.find(iter->first) == threads.end()) {
          iter = cache_.erase(iter);
        } else {
          ++iter;
        }
      }
  
      for (auto tid : threads) {
        if (black_list != nullptr && black_list->find(tid) != black_list->end()) {
          continue;
        }
        sampleAndLogForThread(tid, requested_stats_mask);
      }
    } catch (const std::system_error& e) {
      // threadListFromProcFs can throw an error. Ignore it.
      return;
    }
  }
  
  void ThreadCache::sampleAndLogForThread(
      int32_t tid,
      uint32_t requested_stats_mask) {
    auto statIter = cache_.find(tid);
    if (statIter == cache_.end()) {
      cache_.emplace(std::make_pair(tid, ThreadStatHolder(tid)));
    }
    auto& statHolder = cache_.at(tid);
    try {
      statHolder.sampleAndLog(requested_stats_mask, tid);
    } catch (const std::system_error&) {
    } catch (const std::runtime_error&) {
    }
    return;
  }
  
  int32_t ThreadCache::getStatsAvailabililty(int32_t tid) {
    int32_t stats_mask = 0;
    if (cache_.find(tid) != cache_.end()) {
      stats_mask = cache_.at(tid).getInfo().availableStatsMask;
    }
    return stats_mask;
  }
  
  void ThreadCache::clear() {
    cache_.clear();
  }
  
  } // namespace counters
  } // namespace profilo
  } // namespace facebook
  