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
 | 
