diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/base/IOActivityMonitor.cpp | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/netwerk/base/IOActivityMonitor.cpp b/netwerk/base/IOActivityMonitor.cpp new file mode 100644 index 0000000000..76047a7a00 --- /dev/null +++ b/netwerk/base/IOActivityMonitor.cpp @@ -0,0 +1,495 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "IOActivityMonitor.h" +#include "nsPrintfCString.h" +#include "nsSocketTransport2.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/Promise.h" +#include "prerror.h" +#include "prio.h" +#include "prmem.h" +#include <vector> + +using namespace mozilla; +using namespace mozilla::net; + +mozilla::StaticRefPtr<IOActivityMonitor> gInstance; +static Atomic<bool> gActivated(false); +static PRDescIdentity sNetActivityMonitorLayerIdentity; +static PRIOMethods sNetActivityMonitorLayerMethods; +static PRIOMethods* sNetActivityMonitorLayerMethodsPtr = nullptr; + +// Maximum number of activities entries in the monitoring class +#define MAX_ACTIVITY_ENTRIES 1000 + +// ActivityMonitorSecret is stored in the activity monitor layer +// and provides a method to get the location. +// +// A location can be : +// - a TCP or UDP socket. The form will be socket://ip:port +// - a File. The form will be file://path +// +// For other cases, the location will be fd://number +class ActivityMonitorSecret final { + public: + // constructor used for sockets + explicit ActivityMonitorSecret(PRFileDesc* aFd) { + mFd = aFd; + mLocationSet = false; + } + + // constructor used for files + explicit ActivityMonitorSecret(PRFileDesc* aFd, const char* aLocation) { + mFd = aFd; + mLocation.AppendPrintf("file://%s", aLocation); + mLocationSet = true; + } + + nsCString getLocation() { + if (!mLocationSet) { + LazySetLocation(); + } + return mLocation; + } + + private: + // Called to set the location using the FD on the first getLocation() usage + // which is typically when a socket is opened. If done earlier, at + // construction time, the host won't be bound yet. + // + // If the location is a file, it needs to be initialized in the + // constructor. + void LazySetLocation() { + mLocationSet = true; + PRFileDesc* extract = mFd; + while (PR_GetDescType(extract) == PR_DESC_LAYERED) { + if (!extract->lower) { + break; + } + extract = extract->lower; + } + + PRDescType fdType = PR_GetDescType(extract); + // we should not use LazySetLocation for files + MOZ_ASSERT(fdType != PR_DESC_FILE); + + switch (fdType) { + case PR_DESC_SOCKET_TCP: + case PR_DESC_SOCKET_UDP: { + mLocation.AppendPrintf("socket://"); + PRNetAddr addr; + PRStatus status = PR_GetSockName(mFd, &addr); + if (NS_WARN_IF(status == PR_FAILURE)) { + mLocation.AppendPrintf("unknown"); + break; + } + + // grabbing the host + char netAddr[mozilla::net::kNetAddrMaxCStrBufSize] = {0}; + status = PR_NetAddrToString(&addr, netAddr, sizeof(netAddr) - 1); + if (NS_WARN_IF(status == PR_FAILURE) || netAddr[0] == 0) { + mLocation.AppendPrintf("unknown"); + break; + } + mLocation.Append(netAddr); + + // adding the port + uint16_t port; + if (addr.raw.family == PR_AF_INET) { + port = addr.inet.port; + } else { + port = addr.ipv6.port; + } + mLocation.AppendPrintf(":%d", port); + } break; + + // for all other cases, we just send back fd://<value> + default: { + mLocation.AppendLiteral("fd://"); + mLocation.AppendInt(PR_FileDesc2NativeHandle(mFd)); + } + } // end switch + } + + private: + nsCString mLocation; + bool mLocationSet; + PRFileDesc* mFd; +}; + +// FileDesc2Location converts a PRFileDesc into a "location" by +// grabbing the ActivityMonitorSecret in layer->secret +static nsAutoCString FileDesc2Location(PRFileDesc* fd) { + nsAutoCString location; + PRFileDesc* monitorLayer = + PR_GetIdentitiesLayer(fd, sNetActivityMonitorLayerIdentity); + if (!monitorLayer) { + location.AppendPrintf("unknown"); + return location; + } + + ActivityMonitorSecret* secret = (ActivityMonitorSecret*)monitorLayer->secret; + location.AppendPrintf("%s", secret->getLocation().get()); + return location; +} + +// +// Wrappers around the socket APIS +// +static PRStatus nsNetMon_Connect(PRFileDesc* fd, const PRNetAddr* addr, + PRIntervalTime timeout) { + return fd->lower->methods->connect(fd->lower, addr, timeout); +} + +static PRStatus nsNetMon_Close(PRFileDesc* fd) { + if (!fd) { + return PR_FAILURE; + } + PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER); + MOZ_RELEASE_ASSERT( + layer && layer->identity == sNetActivityMonitorLayerIdentity, + "NetActivityMonitor Layer not on top of stack"); + + if (layer->secret) { + delete (ActivityMonitorSecret*)layer->secret; + layer->secret = nullptr; + } + layer->dtor(layer); + return fd->methods->close(fd); +} + +static int32_t nsNetMon_Read(PRFileDesc* fd, void* buf, int32_t len) { + int32_t ret = fd->lower->methods->read(fd->lower, buf, len); + if (ret >= 0) { + IOActivityMonitor::Read(fd, len); + } + return ret; +} + +static int32_t nsNetMon_Write(PRFileDesc* fd, const void* buf, int32_t len) { + int32_t ret = fd->lower->methods->write(fd->lower, buf, len); + if (ret > 0) { + IOActivityMonitor::Write(fd, len); + } + return ret; +} + +static int32_t nsNetMon_Writev(PRFileDesc* fd, const PRIOVec* iov, int32_t size, + PRIntervalTime timeout) { + int32_t ret = fd->lower->methods->writev(fd->lower, iov, size, timeout); + if (ret > 0) { + IOActivityMonitor::Write(fd, size); + } + return ret; +} + +static int32_t nsNetMon_Recv(PRFileDesc* fd, void* buf, int32_t amount, + int flags, PRIntervalTime timeout) { + int32_t ret = + fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout); + if (ret > 0) { + IOActivityMonitor::Read(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_Send(PRFileDesc* fd, const void* buf, int32_t amount, + int flags, PRIntervalTime timeout) { + int32_t ret = + fd->lower->methods->send(fd->lower, buf, amount, flags, timeout); + if (ret > 0) { + IOActivityMonitor::Write(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_RecvFrom(PRFileDesc* fd, void* buf, int32_t amount, + int flags, PRNetAddr* addr, + PRIntervalTime timeout) { + int32_t ret = fd->lower->methods->recvfrom(fd->lower, buf, amount, flags, + addr, timeout); + if (ret > 0) { + IOActivityMonitor::Read(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_SendTo(PRFileDesc* fd, const void* buf, int32_t amount, + int flags, const PRNetAddr* addr, + PRIntervalTime timeout) { + int32_t ret = + fd->lower->methods->sendto(fd->lower, buf, amount, flags, addr, timeout); + if (ret > 0) { + IOActivityMonitor::Write(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_AcceptRead(PRFileDesc* listenSock, + PRFileDesc** acceptedSock, + PRNetAddr** peerAddr, void* buf, + int32_t amount, PRIntervalTime timeout) { + int32_t ret = listenSock->lower->methods->acceptread( + listenSock->lower, acceptedSock, peerAddr, buf, amount, timeout); + if (ret > 0) { + IOActivityMonitor::Read(listenSock, amount); + } + return ret; +} + +// +// Class IOActivityMonitor +// +NS_IMPL_ISUPPORTS(IOActivityMonitor, nsINamed) + +IOActivityMonitor::IOActivityMonitor() : mLock("IOActivityMonitor::mLock") { + RefPtr<IOActivityMonitor> mon(gInstance); + MOZ_ASSERT(!mon, "multiple IOActivityMonitor instances!"); +} + +// static +void IOActivityMonitor::RequestActivities(dom::Promise* aPromise) { + MOZ_ASSERT(aPromise); + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + aPromise->MaybeReject(NS_ERROR_FAILURE); + return; + } + + mon->RequestActivitiesInternal(aPromise); +} + +void IOActivityMonitor::RequestActivitiesInternal(dom::Promise* aPromise) { + nsresult result = NS_OK; + FallibleTArray<dom::IOActivityDataDictionary> activities; + + { + mozilla::MutexAutoLock lock(mLock); + // Remove inactive activities + for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) { + dom::IOActivityDataDictionary* activity = &iter.Data(); + if (activity->mRx == 0 && activity->mTx == 0) { + iter.Remove(); + } else { + if (NS_WARN_IF(!activities.AppendElement(iter.Data(), fallible))) { + result = NS_ERROR_OUT_OF_MEMORY; + break; + } + } + } + } + + if (NS_WARN_IF(NS_FAILED(result))) { + aPromise->MaybeReject(result); + return; + } + aPromise->MaybeResolve(activities); +} + +NS_IMETHODIMP +IOActivityMonitor::GetName(nsACString& aName) { + aName.AssignLiteral("IOActivityMonitor"); + return NS_OK; +} + +// static +bool IOActivityMonitor::IsActive() { return gActivated; } + +// static +already_AddRefed<IOActivityMonitor> IOActivityMonitor::Get() { + if (!gActivated) { + return nullptr; + } + + RefPtr<IOActivityMonitor> mon = gInstance; + return mon.forget(); +} + +nsresult IOActivityMonitor::Init() { + if (IsActive()) { + return NS_ERROR_ALREADY_INITIALIZED; + } + RefPtr<IOActivityMonitor> mon = new IOActivityMonitor(); + nsresult rv = mon->InitInternal(); + if (NS_SUCCEEDED(rv)) { + gInstance = mon; + ClearOnShutdown(&gInstance); + gActivated = true; + } + return rv; +} + +nsresult IOActivityMonitor::InitInternal() { + // wraps the socket APIs + if (!sNetActivityMonitorLayerMethodsPtr) { + sNetActivityMonitorLayerIdentity = + PR_GetUniqueIdentity("network activity monitor layer"); + sNetActivityMonitorLayerMethods = *PR_GetDefaultIOMethods(); + sNetActivityMonitorLayerMethods.connect = nsNetMon_Connect; + sNetActivityMonitorLayerMethods.read = nsNetMon_Read; + sNetActivityMonitorLayerMethods.write = nsNetMon_Write; + sNetActivityMonitorLayerMethods.writev = nsNetMon_Writev; + sNetActivityMonitorLayerMethods.recv = nsNetMon_Recv; + sNetActivityMonitorLayerMethods.send = nsNetMon_Send; + sNetActivityMonitorLayerMethods.recvfrom = nsNetMon_RecvFrom; + sNetActivityMonitorLayerMethods.sendto = nsNetMon_SendTo; + sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead; + sNetActivityMonitorLayerMethods.close = nsNetMon_Close; + sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods; + } + + return NS_OK; +} + +nsresult IOActivityMonitor::Shutdown() { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_NOT_INITIALIZED; + } + return mon->ShutdownInternal(); +} + +nsresult IOActivityMonitor::ShutdownInternal() { + mozilla::MutexAutoLock lock(mLock); + mActivities.Clear(); + gActivated = false; + return NS_OK; +} + +nsresult IOActivityMonitor::MonitorSocket(PRFileDesc* aFd) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_OK; + } + PRFileDesc* layer; + PRStatus status; + layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity, + sNetActivityMonitorLayerMethodsPtr); + if (!layer) { + return NS_ERROR_FAILURE; + } + + ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd); + layer->secret = reinterpret_cast<PRFilePrivate*>(secret); + status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer); + + if (status == PR_FAILURE) { + delete secret; + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult IOActivityMonitor::MonitorFile(PRFileDesc* aFd, const char* aPath) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_OK; + } + PRFileDesc* layer; + PRStatus status; + layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity, + sNetActivityMonitorLayerMethodsPtr); + if (!layer) { + return NS_ERROR_FAILURE; + } + + ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd, aPath); + layer->secret = reinterpret_cast<PRFilePrivate*>(secret); + + status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer); + if (status == PR_FAILURE) { + delete secret; + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool IOActivityMonitor::IncrementActivity(const nsACString& aLocation, + uint32_t aRx, uint32_t aTx) { + mLock.AssertCurrentThreadOwns(); + return mActivities.WithEntryHandle(aLocation, fallible, [&](auto&& entry) { + if (!entry) return false; + + if (*entry) { + // already registered + entry->Data().mTx += aTx; + entry->Data().mRx += aRx; + } else { + // Creating a new IOActivity. Notice that mActivities + // will grow indefinitely, which is OK since we won't + // have but a few hundreds entries at the most, but we + // want to assert we have at the most 1000 entries + MOZ_ASSERT(mActivities.Count() <= MAX_ACTIVITY_ENTRIES); + + dom::IOActivityDataDictionary activity; + activity.mLocation.Assign(aLocation); + activity.mTx = aTx; + activity.mRx = aRx; + + entry->Insert(std::move(activity)); + } + + return true; + }); +} + +nsresult IOActivityMonitor::Write(const nsACString& aLocation, + uint32_t aAmount) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->WriteInternal(aLocation, aAmount); +} + +nsresult IOActivityMonitor::Write(PRFileDesc* fd, uint32_t aAmount) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->Write(FileDesc2Location(fd), aAmount); +} + +nsresult IOActivityMonitor::WriteInternal(const nsACString& aLocation, + uint32_t aAmount) { + mozilla::MutexAutoLock lock(mLock); + if (!IncrementActivity(aLocation, aAmount, 0)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult IOActivityMonitor::Read(PRFileDesc* fd, uint32_t aAmount) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->Read(FileDesc2Location(fd), aAmount); +} + +nsresult IOActivityMonitor::Read(const nsACString& aLocation, + uint32_t aAmount) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->ReadInternal(aLocation, aAmount); +} + +nsresult IOActivityMonitor::ReadInternal(const nsACString& aLocation, + uint32_t aAmount) { + mozilla::MutexAutoLock lock(mLock); + if (!IncrementActivity(aLocation, 0, aAmount)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} |