CheckerboardReportService.cpp (7620B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "CheckerboardReportService.h" 8 9 #include "jsapi.h" // for JS_Now 10 #include "MainThreadUtils.h" // for NS_IsMainThread 11 #include "mozilla/Assertions.h" // for MOZ_ASSERT 12 #include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown 13 #include "mozilla/Preferences.h" 14 #include "mozilla/Services.h" 15 #include "mozilla/StaticPrefs_apz.h" 16 #include "mozilla/dom/CheckerboardReportServiceBinding.h" // for dom::CheckerboardReports 17 #include "mozilla/gfx/GPUParent.h" 18 #include "mozilla/gfx/GPUProcessManager.h" 19 #include "nsContentUtils.h" // for nsContentUtils 20 #include "nsIObserverService.h" 21 #include "nsXULAppAPI.h" 22 23 namespace mozilla { 24 namespace layers { 25 26 /*static*/ 27 StaticRefPtr<CheckerboardEventStorage> CheckerboardEventStorage::sInstance; 28 29 /*static*/ 30 already_AddRefed<CheckerboardEventStorage> 31 CheckerboardEventStorage::GetInstance() { 32 // The instance in the parent process does all the work, so if this is getting 33 // called in the child process something is likely wrong. 34 MOZ_ASSERT(XRE_IsParentProcess()); 35 36 MOZ_ASSERT(NS_IsMainThread()); 37 if (!sInstance) { 38 sInstance = new CheckerboardEventStorage(); 39 ClearOnShutdown(&sInstance); 40 } 41 RefPtr<CheckerboardEventStorage> instance = sInstance.get(); 42 return instance.forget(); 43 } 44 45 void CheckerboardEventStorage::Report(uint32_t aSeverity, 46 const std::string& aLog) { 47 if (!NS_IsMainThread()) { 48 RefPtr<Runnable> task = NS_NewRunnableFunction( 49 "layers::CheckerboardEventStorage::Report", 50 [aSeverity, aLog]() -> void { 51 CheckerboardEventStorage::Report(aSeverity, aLog); 52 }); 53 NS_DispatchToMainThread(task.forget()); 54 return; 55 } 56 57 if (XRE_IsGPUProcess()) { 58 if (gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton()) { 59 nsCString log(aLog.c_str()); 60 (void)gpu->SendReportCheckerboard(aSeverity, log); 61 } 62 return; 63 } 64 65 RefPtr<CheckerboardEventStorage> storage = GetInstance(); 66 storage->ReportCheckerboard(aSeverity, aLog); 67 } 68 69 void CheckerboardEventStorage::ReportCheckerboard(uint32_t aSeverity, 70 const std::string& aLog) { 71 MOZ_ASSERT(NS_IsMainThread()); 72 73 if (aSeverity == 0) { 74 // This code assumes all checkerboard reports have a nonzero severity. 75 return; 76 } 77 78 CheckerboardReport severe(aSeverity, JS_Now(), aLog); 79 CheckerboardReport recent; 80 81 // First look in the "severe" reports to see if the new one belongs in that 82 // list. 83 for (int i = 0; i < SEVERITY_MAX_INDEX; i++) { 84 if (mCheckerboardReports[i].mSeverity >= severe.mSeverity) { 85 continue; 86 } 87 // The new one deserves to be in the "severe" list. Take the one getting 88 // bumped off the list, and put it in |recent| for possible insertion into 89 // the recents list. 90 recent = mCheckerboardReports[SEVERITY_MAX_INDEX - 1]; 91 92 // Shuffle the severe list down, insert the new one. 93 for (int j = SEVERITY_MAX_INDEX - 1; j > i; j--) { 94 mCheckerboardReports[j] = mCheckerboardReports[j - 1]; 95 } 96 mCheckerboardReports[i] = severe; 97 severe.mSeverity = 0; // mark |severe| as inserted 98 break; 99 } 100 101 // If |severe.mSeverity| is nonzero, the incoming report didn't get inserted 102 // into the severe list; put it into |recent| for insertion into the recent 103 // list. 104 if (severe.mSeverity) { 105 MOZ_ASSERT(recent.mSeverity == 0, "recent should be empty here"); 106 recent = severe; 107 } // else |recent| may hold a report that got knocked out of the severe list. 108 109 if (recent.mSeverity == 0) { 110 // Nothing to be inserted into the recent list. 111 return; 112 } 113 114 // If it wasn't in the "severe" list, add it to the "recent" list. 115 for (int i = SEVERITY_MAX_INDEX; i < RECENT_MAX_INDEX; i++) { 116 if (mCheckerboardReports[i].mTimestamp >= recent.mTimestamp) { 117 continue; 118 } 119 // |recent| needs to be inserted at |i|. Shuffle the remaining ones down 120 // and insert it. 121 for (int j = RECENT_MAX_INDEX - 1; j > i; j--) { 122 mCheckerboardReports[j] = mCheckerboardReports[j - 1]; 123 } 124 mCheckerboardReports[i] = recent; 125 break; 126 } 127 } 128 129 void CheckerboardEventStorage::GetReports( 130 nsTArray<dom::CheckerboardReport>& aOutReports) { 131 MOZ_ASSERT(NS_IsMainThread()); 132 133 for (int i = 0; i < RECENT_MAX_INDEX; i++) { 134 CheckerboardReport& r = mCheckerboardReports[i]; 135 if (r.mSeverity == 0) { 136 continue; 137 } 138 dom::CheckerboardReport report; 139 report.mSeverity.Construct() = r.mSeverity; 140 report.mTimestamp.Construct() = r.mTimestamp / 1000; // micros to millis 141 report.mLog.Construct() = 142 NS_ConvertUTF8toUTF16(r.mLog.c_str(), r.mLog.size()); 143 report.mReason.Construct() = (i < SEVERITY_MAX_INDEX) 144 ? dom::CheckerboardReason::Severe 145 : dom::CheckerboardReason::Recent; 146 aOutReports.AppendElement(report); 147 } 148 } 149 150 } // namespace layers 151 152 namespace dom { 153 154 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CheckerboardReportService, mParent) 155 156 /*static*/ 157 bool CheckerboardReportService::IsEnabled(JSContext* aCtx, JSObject* aGlobal) { 158 // Only allow this in the parent process 159 if (!XRE_IsParentProcess()) { 160 return false; 161 } 162 // Allow privileged code or about:checkerboard (unprivileged) to access this. 163 return nsContentUtils::IsSystemCaller(aCtx) || 164 nsContentUtils::IsSpecificAboutPage(aGlobal, "about:checkerboard"); 165 } 166 167 /*static*/ 168 already_AddRefed<CheckerboardReportService> 169 CheckerboardReportService::Constructor(const dom::GlobalObject& aGlobal) { 170 RefPtr<CheckerboardReportService> ces = 171 new CheckerboardReportService(aGlobal.GetAsSupports()); 172 return ces.forget(); 173 } 174 175 CheckerboardReportService::CheckerboardReportService(nsISupports* aParent) 176 : mParent(aParent) {} 177 178 JSObject* CheckerboardReportService::WrapObject( 179 JSContext* aCtx, JS::Handle<JSObject*> aGivenProto) { 180 return CheckerboardReportService_Binding::Wrap(aCtx, this, aGivenProto); 181 } 182 183 nsISupports* CheckerboardReportService::GetParentObject() { return mParent; } 184 185 void CheckerboardReportService::GetReports( 186 nsTArray<dom::CheckerboardReport>& aOutReports) { 187 RefPtr<mozilla::layers::CheckerboardEventStorage> instance = 188 mozilla::layers::CheckerboardEventStorage::GetInstance(); 189 MOZ_ASSERT(instance); 190 instance->GetReports(aOutReports); 191 } 192 193 bool CheckerboardReportService::IsRecordingEnabled() const { 194 return StaticPrefs::apz_record_checkerboarding(); 195 } 196 197 void CheckerboardReportService::SetRecordingEnabled(bool aEnabled) { 198 Preferences::SetBool("apz.record_checkerboarding", aEnabled); 199 } 200 201 void CheckerboardReportService::FlushActiveReports() { 202 MOZ_ASSERT(XRE_IsParentProcess()); 203 gfx::GPUProcessManager* gpm = gfx::GPUProcessManager::Get(); 204 if (gpm && gpm->NotifyGpuObservers("APZ:FlushActiveCheckerboard")) { 205 return; 206 } 207 208 // We failed to dispatch the observer event, either because we have shutdown 209 // or the GPU process is temporarily down. In that case, let the callers know 210 // we are done. 211 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService(); 212 MOZ_ASSERT(obsSvc); 213 if (obsSvc) { 214 obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard:Done", 215 nullptr); 216 } 217 } 218 219 } // namespace dom 220 } // namespace mozilla