forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			344 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			344 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "mozilla/ProcInfo.h"
 | 
						|
#include "mozilla/ProcInfo_linux.h"
 | 
						|
#include "mozilla/Sprintf.h"
 | 
						|
#include "mozilla/Logging.h"
 | 
						|
#include "mozilla/ScopeExit.h"
 | 
						|
#include "mozilla/ipc/GeckoChildProcessHost.h"
 | 
						|
#include "nsMemoryReporterManager.h"
 | 
						|
#include "nsWhitespaceTokenizer.h"
 | 
						|
 | 
						|
#include <cstdio>
 | 
						|
#include <cstring>
 | 
						|
#include <unistd.h>
 | 
						|
#include <dirent.h>
 | 
						|
 | 
						|
#define NANOPERSEC 1000000000.
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
 | 
						|
int GetCycleTimeFrequencyMHz() { return 0; }
 | 
						|
 | 
						|
// StatReader can parse and tokenize a POSIX stat file.
 | 
						|
// see http://man7.org/linux/man-pages/man5/proc.5.html
 | 
						|
//
 | 
						|
// Its usage is quite simple:
 | 
						|
//
 | 
						|
// StatReader reader(pid);
 | 
						|
// ProcInfo info;
 | 
						|
// rv = reader.ParseProc(info);
 | 
						|
// if (NS_FAILED(rv)) {
 | 
						|
//     // the reading of the file or its parsing failed.
 | 
						|
// }
 | 
						|
//
 | 
						|
class StatReader {
 | 
						|
 public:
 | 
						|
  explicit StatReader(const base::ProcessId aPid)
 | 
						|
      : mPid(aPid), mMaxIndex(15), mTicksPerSec(sysconf(_SC_CLK_TCK)) {}
 | 
						|
 | 
						|
  nsresult ParseProc(ProcInfo& aInfo) {
 | 
						|
    nsAutoString fileContent;
 | 
						|
    nsresult rv = ReadFile(fileContent);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
    // We first extract the file or thread name
 | 
						|
    int32_t startPos = fileContent.RFindChar('(');
 | 
						|
    if (startPos == -1) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    int32_t endPos = fileContent.RFindChar(')');
 | 
						|
    if (endPos == -1) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    int32_t len = endPos - (startPos + 1);
 | 
						|
    mName.Assign(Substring(fileContent, startPos + 1, len));
 | 
						|
 | 
						|
    // now we can use the tokenizer for the rest of the file
 | 
						|
    nsWhitespaceTokenizer tokenizer(Substring(fileContent, endPos + 2));
 | 
						|
    int32_t index = 2;  // starting at third field
 | 
						|
    while (tokenizer.hasMoreTokens() && index < mMaxIndex) {
 | 
						|
      const nsAString& token = tokenizer.nextToken();
 | 
						|
      rv = UseToken(index, token, aInfo);
 | 
						|
      NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
      index++;
 | 
						|
    }
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
 protected:
 | 
						|
  // Called for each token found in the stat file.
 | 
						|
  nsresult UseToken(int32_t aIndex, const nsAString& aToken, ProcInfo& aInfo) {
 | 
						|
    // We're using a subset of what stat has to offer for now.
 | 
						|
    nsresult rv = NS_OK;
 | 
						|
    // see the proc documentation for fields index references.
 | 
						|
    switch (aIndex) {
 | 
						|
      case 13:
 | 
						|
        // Amount of time that this process has been scheduled
 | 
						|
        // in user mode, measured in clock ticks
 | 
						|
        aInfo.cpuTime += GetCPUTime(aToken, &rv);
 | 
						|
        NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
        break;
 | 
						|
      case 14:
 | 
						|
        // Amount of time that this process has been scheduled
 | 
						|
        // in kernel mode, measured in clock ticks
 | 
						|
        aInfo.cpuTime += GetCPUTime(aToken, &rv);
 | 
						|
        NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
        break;
 | 
						|
    }
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // Converts a token into a int64_t
 | 
						|
  uint64_t Get64Value(const nsAString& aToken, nsresult* aRv) {
 | 
						|
    // We can't use aToken.ToInteger64() since it returns a signed 64.
 | 
						|
    // and that can result into an overflow.
 | 
						|
    nsresult rv = NS_OK;
 | 
						|
    uint64_t out = 0;
 | 
						|
    if (sscanf(NS_ConvertUTF16toUTF8(aToken).get(), "%" PRIu64, &out) == 0) {
 | 
						|
      rv = NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    *aRv = rv;
 | 
						|
    return out;
 | 
						|
  }
 | 
						|
 | 
						|
  // Converts a token into CPU time in nanoseconds.
 | 
						|
  uint64_t GetCPUTime(const nsAString& aToken, nsresult* aRv) {
 | 
						|
    nsresult rv;
 | 
						|
    uint64_t value = Get64Value(aToken, &rv);
 | 
						|
    *aRv = rv;
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
    if (value) {
 | 
						|
      value = (value * NANOPERSEC) / mTicksPerSec;
 | 
						|
    }
 | 
						|
    return value;
 | 
						|
  }
 | 
						|
 | 
						|
  base::ProcessId mPid;
 | 
						|
  int32_t mMaxIndex;
 | 
						|
  nsCString mFilepath;
 | 
						|
  nsString mName;
 | 
						|
 | 
						|
 private:
 | 
						|
  // Reads the stat file and puts its content in a nsString.
 | 
						|
  nsresult ReadFile(nsAutoString& aFileContent) {
 | 
						|
    if (mFilepath.IsEmpty()) {
 | 
						|
      if (mPid == 0) {
 | 
						|
        mFilepath.AssignLiteral("/proc/self/stat");
 | 
						|
      } else {
 | 
						|
        mFilepath.AppendPrintf("/proc/%u/stat", unsigned(mPid));
 | 
						|
      }
 | 
						|
    }
 | 
						|
    FILE* fstat = fopen(mFilepath.get(), "r");
 | 
						|
    if (!fstat) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    // /proc is a virtual file system and all files are
 | 
						|
    // of size 0, so GetFileSize() and related functions will
 | 
						|
    // return 0 - so the way to read the file is to fill a buffer
 | 
						|
    // of an arbitrary big size and look for the end of line char.
 | 
						|
    char buffer[2048];
 | 
						|
    char* end;
 | 
						|
    char* start = fgets(buffer, 2048, fstat);
 | 
						|
    fclose(fstat);
 | 
						|
    if (start == nullptr) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    // let's find the end
 | 
						|
    end = strchr(buffer, '\n');
 | 
						|
    if (!end) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    aFileContent.AssignASCII(buffer, size_t(end - start));
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  int64_t mTicksPerSec;
 | 
						|
};
 | 
						|
 | 
						|
// Threads have the same stat file. The only difference is its path
 | 
						|
// and we're getting less info in the ThreadInfo structure.
 | 
						|
class ThreadInfoReader final : public StatReader {
 | 
						|
 public:
 | 
						|
  ThreadInfoReader(const base::ProcessId aPid, const base::ProcessId aTid)
 | 
						|
      : StatReader(aPid) {
 | 
						|
    mFilepath.AppendPrintf("/proc/%u/task/%u/stat", unsigned(aPid),
 | 
						|
                           unsigned(aTid));
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult ParseThread(ThreadInfo& aInfo) {
 | 
						|
    ProcInfo info;
 | 
						|
    nsresult rv = StatReader::ParseProc(info);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    // Copying over the data we got from StatReader::ParseProc()
 | 
						|
    aInfo.cpuTime = info.cpuTime;
 | 
						|
    aInfo.name.Assign(mName);
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
nsresult GetCpuTimeSinceProcessStartInMs(uint64_t* aResult) {
 | 
						|
  timespec t;
 | 
						|
  if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t) == 0) {
 | 
						|
    uint64_t cpuTime =
 | 
						|
        uint64_t(t.tv_sec) * 1'000'000'000u + uint64_t(t.tv_nsec);
 | 
						|
    *aResult = cpuTime / PR_NSEC_PER_MSEC;
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  StatReader reader(0);
 | 
						|
  ProcInfo info;
 | 
						|
  nsresult rv = reader.ParseProc(info);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  *aResult = info.cpuTime / PR_NSEC_PER_MSEC;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult GetGpuTimeSinceProcessStartInMs(uint64_t* aResult) {
 | 
						|
  return NS_ERROR_NOT_IMPLEMENTED;
 | 
						|
}
 | 
						|
 | 
						|
ProcInfoPromise::ResolveOrRejectValue GetProcInfoSync(
 | 
						|
    nsTArray<ProcInfoRequest>&& aRequests) {
 | 
						|
  ProcInfoPromise::ResolveOrRejectValue result;
 | 
						|
 | 
						|
  HashMap<base::ProcessId, ProcInfo> gathered;
 | 
						|
  if (!gathered.reserve(aRequests.Length())) {
 | 
						|
    result.SetReject(NS_ERROR_OUT_OF_MEMORY);
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
  for (const auto& request : aRequests) {
 | 
						|
    ProcInfo info;
 | 
						|
 | 
						|
    timespec t;
 | 
						|
    clockid_t clockid = MAKE_PROCESS_CPUCLOCK(request.pid, CPUCLOCK_SCHED);
 | 
						|
    if (clock_gettime(clockid, &t) == 0) {
 | 
						|
      info.cpuTime = uint64_t(t.tv_sec) * 1'000'000'000u + uint64_t(t.tv_nsec);
 | 
						|
    } else {
 | 
						|
      // Fallback to parsing /proc/<pid>/stat
 | 
						|
      StatReader reader(request.pid);
 | 
						|
      nsresult rv = reader.ParseProc(info);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        // Can't read data for this proc.
 | 
						|
        // Probably either a sandboxing issue or a race condition, e.g.
 | 
						|
        // the process has been just been killed. Regardless, skip process.
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // The 'Memory' value displayed in the system monitor is resident -
 | 
						|
    // shared. statm contains more fields, but we're only interested in
 | 
						|
    // the first three.
 | 
						|
    static const int MAX_FIELD = 3;
 | 
						|
    size_t VmSize, resident, shared;
 | 
						|
    info.memory = 0;
 | 
						|
    FILE* f = fopen(nsPrintfCString("/proc/%u/statm", request.pid).get(), "r");
 | 
						|
    if (f) {
 | 
						|
      int nread = fscanf(f, "%zu %zu %zu", &VmSize, &resident, &shared);
 | 
						|
      fclose(f);
 | 
						|
      if (nread == MAX_FIELD) {
 | 
						|
        info.memory = (resident - shared) * getpagesize();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Extra info
 | 
						|
    info.pid = request.pid;
 | 
						|
    info.childId = request.childId;
 | 
						|
    info.type = request.processType;
 | 
						|
    info.origin = request.origin;
 | 
						|
    info.windows = std::move(request.windowInfo);
 | 
						|
    info.utilityActors = std::move(request.utilityInfo);
 | 
						|
 | 
						|
    // Let's look at the threads
 | 
						|
    nsCString taskPath;
 | 
						|
    taskPath.AppendPrintf("/proc/%u/task", unsigned(request.pid));
 | 
						|
    DIR* dirHandle = opendir(taskPath.get());
 | 
						|
    if (!dirHandle) {
 | 
						|
      // For some reason, we have no data on the threads for this process.
 | 
						|
      // Most likely reason is that we have just lost a race condition and
 | 
						|
      // the process is dead.
 | 
						|
      // Let's stop here and ignore the entire process.
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    auto cleanup = mozilla::MakeScopeExit([&] { closedir(dirHandle); });
 | 
						|
 | 
						|
    // If we can't read some thread info, we ignore that thread.
 | 
						|
    dirent* entry;
 | 
						|
    while ((entry = readdir(dirHandle)) != nullptr) {
 | 
						|
      if (entry->d_name[0] == '.') {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      nsAutoCString entryName(entry->d_name);
 | 
						|
      nsresult rv;
 | 
						|
      int32_t tid = entryName.ToInteger(&rv);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      ThreadInfo threadInfo;
 | 
						|
      threadInfo.tid = tid;
 | 
						|
 | 
						|
      timespec ts;
 | 
						|
      if (clock_gettime(MAKE_THREAD_CPUCLOCK(tid, CPUCLOCK_SCHED), &ts) == 0) {
 | 
						|
        threadInfo.cpuTime =
 | 
						|
            uint64_t(ts.tv_sec) * 1'000'000'000u + uint64_t(ts.tv_nsec);
 | 
						|
 | 
						|
        nsCString path;
 | 
						|
        path.AppendPrintf("/proc/%u/task/%u/comm", unsigned(request.pid),
 | 
						|
                          unsigned(tid));
 | 
						|
        FILE* fstat = fopen(path.get(), "r");
 | 
						|
        if (fstat) {
 | 
						|
          // /proc is a virtual file system and all files are
 | 
						|
          // of size 0, so GetFileSize() and related functions will
 | 
						|
          // return 0 - so the way to read the file is to fill a buffer
 | 
						|
          // of an arbitrary big size and look for the end of line char.
 | 
						|
          // The size of the buffer needs to be as least 16, which is the
 | 
						|
          // value of TASK_COMM_LEN in the Linux kernel.
 | 
						|
          char buffer[32];
 | 
						|
          char* start = fgets(buffer, sizeof(buffer), fstat);
 | 
						|
          fclose(fstat);
 | 
						|
          if (start) {
 | 
						|
            // The thread name should always be smaller than our buffer,
 | 
						|
            // so we should find a newline character.
 | 
						|
            char* end = strchr(buffer, '\n');
 | 
						|
            if (end) {
 | 
						|
              threadInfo.name.AssignASCII(buffer, size_t(end - start));
 | 
						|
              info.threads.AppendElement(threadInfo);
 | 
						|
              continue;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // Fallback to parsing /proc/<pid>/task/<tid>/stat
 | 
						|
      // This is needed for child processes, as access to the per-thread
 | 
						|
      // CPU clock is restricted to the process owning the thread.
 | 
						|
      ThreadInfoReader reader(request.pid, tid);
 | 
						|
      rv = reader.ParseThread(threadInfo);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      info.threads.AppendElement(threadInfo);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!gathered.put(request.pid, std::move(info))) {
 | 
						|
      result.SetReject(NS_ERROR_OUT_OF_MEMORY);
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // ... and we're done!
 | 
						|
  result.SetResolve(std::move(gathered));
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla
 |