ProgressLogger.h (21660B)
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 #ifndef ProgressLogger_h 8 #define ProgressLogger_h 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/ProportionValue.h" 12 #include "mozilla/RefCounted.h" 13 #include "mozilla/RefPtr.h" 14 15 #include <atomic> 16 17 // Uncomment to printf ProcessLogger updates. 18 // #define DEBUG_PROCESSLOGGER 19 20 #ifdef DEBUG_PROCESSLOGGER 21 # include "mozilla/BaseProfilerUtils.h" 22 # include <cstdio> 23 #endif // DEBUG_PROCESSLOGGER 24 25 namespace mozilla { 26 27 // A `ProgressLogger` is used to update a referenced atomic `ProportionValue`, 28 // and can recursively create a sub-logger corresponding to a subset of their 29 // own range, but that sub-logger's updates are done in its local 0%-100% range. 30 // The typical usage is for multi-level tasks, where each level can estimate its 31 // own work and the work delegated to a next-level function, without knowing how 32 // this local work relates to the higher-level total work. See 33 // `CreateSubLoggerFromTo` for details. 34 // Note that this implementation is single-threaded, it does not support logging 35 // progress from multiple threads at the same time. 36 class ProgressLogger { 37 public: 38 // An RefPtr'd object of this class is used as the target of all 39 // ProgressLogger updates, and it may be shared to make these updates visible 40 // from other code in any thread. 41 class SharedProgress : public external::AtomicRefCounted<SharedProgress> { 42 public: 43 MOZ_DECLARE_REFCOUNTED_TYPENAME(SharedProgress) 44 45 SharedProgress() = default; 46 47 SharedProgress(const SharedProgress&) = delete; 48 SharedProgress& operator=(const SharedProgress&) = delete; 49 50 // This constant is used to indicate that an update may change the progress 51 // value, but should not modify the previously-recorded location. 52 static constexpr const char* NO_LOCATION_UPDATE = nullptr; 53 54 // Set the current progress and location, but the previous location is not 55 // overwritten if the new one is null or empty. 56 // The location and then the progress are atomically "released", so that all 57 // preceding writes on this thread will be visible to other threads reading 58 // these values; most importantly when reaching 100% progress, the reader 59 // can be confident that the location is final and the operation being 60 // watched has completed. 61 void SetProgress( 62 ProportionValue aProgress, 63 const char* aLocationOrNullEmptyToIgnore = NO_LOCATION_UPDATE) { 64 if (aLocationOrNullEmptyToIgnore && 65 *aLocationOrNullEmptyToIgnore != '\0') { 66 mLastLocation.store(aLocationOrNullEmptyToIgnore, 67 std::memory_order_release); 68 } 69 mProgress.store(aProgress, std::memory_order_release); 70 } 71 72 // Read the current progress value. Atomically "acquired", so that writes 73 // from the thread that stored this value are all visible to the reader 74 // here; most importantly when reaching 100%, we can be confident that the 75 // location is final and the operation being watched has completed. 76 [[nodiscard]] ProportionValue Progress() const { 77 return mProgress.load(std::memory_order_acquire); 78 } 79 80 // Read the current progress value. Atomically "acquired". 81 [[nodiscard]] const char* LastLocation() const { 82 return mLastLocation.load(std::memory_order_acquire); 83 } 84 85 private: 86 friend mozilla::detail::RefCounted<SharedProgress, 87 mozilla::detail::AtomicRefCount>; 88 ~SharedProgress() = default; 89 90 // Progress and last-known location. 91 // Beware that these two values are not strongly tied: Reading one then the 92 // other may give mismatched information; but it should be fine for 93 // informational usage. 94 // They are stored using atomic acquire-release ordering, to guarantee that 95 // when read, all writes preceding these values are visible. 96 std::atomic<ProportionValue> mProgress = ProportionValue{0.0}; 97 std::atomic<const char*> mLastLocation = nullptr; 98 }; 99 100 static constexpr const char* NO_LOCATION_UPDATE = 101 SharedProgress::NO_LOCATION_UPDATE; 102 103 ProgressLogger() = default; 104 105 // Construct a top-level logger, starting at 0% and expected to end at 100%. 106 explicit ProgressLogger( 107 RefPtr<SharedProgress> aGlobalProgressOrNull, 108 const char* aLocationOrNullEmptyToIgnoreAtStart = NO_LOCATION_UPDATE, 109 const char* aLocationOrNullEmptyToIgnoreAtEnd = NO_LOCATION_UPDATE) 110 : ProgressLogger{std::move(aGlobalProgressOrNull), 111 /* Start */ ProportionValue{0.0}, 112 /* Multiplier */ ProportionValue{1.0}, 113 aLocationOrNullEmptyToIgnoreAtStart, 114 aLocationOrNullEmptyToIgnoreAtEnd} {} 115 116 // Don't make copies, it would be confusing! 117 // TODO: Copies could one day be allowed to track multi-threaded work, but it 118 // is outside the scope of this implementation; Please update if needed. 119 ProgressLogger(const ProgressLogger&) = delete; 120 ProgressLogger& operator&(const ProgressLogger&) = delete; 121 122 // Move-construct is allowed, to return from CreateSubLoggerFromTo, and 123 // forward straight into a function. Note that moved-from ProgressLoggers must 124 // not be used anymore! Use `CreateSubLoggerFromTo` to pass a sub-logger to 125 // functions. 126 ProgressLogger(ProgressLogger&& aOther) 127 : mGlobalProgressOrNull(std::move(aOther.mGlobalProgressOrNull)), 128 mLocalStartInGlobalSpace(aOther.mLocalStartInGlobalSpace), 129 mLocalToGlobalMultiplier(aOther.mLocalToGlobalMultiplier), 130 mLocationAtDestruction(aOther.mLocationAtDestruction) { 131 aOther.MarkMovedFrom(); 132 #ifdef DEBUG_PROCESSLOGGER 133 if (mGlobalProgressOrNull) { 134 printf("[%d] Moved (staying globally at %.2f in [%.2f, %.2f])\n", 135 int(baseprofiler::profiler_current_process_id().ToNumber()), 136 GetGlobalProgress().ToDouble() * 100.0, 137 mLocalStartInGlobalSpace.ToDouble() * 100.0, 138 (mLocalStartInGlobalSpace + mLocalToGlobalMultiplier).ToDouble() * 139 100.0); 140 } 141 #endif // DEBUG_PROCESSLOGGER 142 } 143 144 // Move-assign. This may be useful when starting with a default (empty) logger 145 // and later assigning it a progress value to start updating. 146 ProgressLogger& operator=(ProgressLogger&& aOther) { 147 mGlobalProgressOrNull = std::move(aOther.mGlobalProgressOrNull); 148 mLocalStartInGlobalSpace = aOther.mLocalStartInGlobalSpace; 149 mLocalToGlobalMultiplier = aOther.mLocalToGlobalMultiplier; 150 mLocationAtDestruction = aOther.mLocationAtDestruction; 151 aOther.MarkMovedFrom(); 152 #ifdef DEBUG_PROCESSLOGGER 153 if (mGlobalProgressOrNull) { 154 printf("[%d] Re-assigned (globally at %.2f in [%.2f, %.2f])\n", 155 int(baseprofiler::profiler_current_process_id().ToNumber()), 156 GetGlobalProgress().ToDouble() * 100.0, 157 mLocalStartInGlobalSpace.ToDouble() * 100.0, 158 (mLocalStartInGlobalSpace + mLocalToGlobalMultiplier).ToDouble() * 159 100.0); 160 } 161 #endif // DEBUG_PROCESSLOGGER 162 return *this; 163 } 164 165 // Destruction sets the local update value to 100% unless empty or moved-from. 166 ~ProgressLogger() { 167 if (!IsMovedFrom()) { 168 #ifdef DEBUG_PROCESSLOGGER 169 if (mGlobalProgressOrNull) { 170 printf("[%d] Destruction:\n", 171 int(baseprofiler::profiler_current_process_id().ToNumber())); 172 } 173 #endif // DEBUG_PROCESSLOGGER 174 SetLocalProgress(ProportionValue{1.0}, mLocationAtDestruction); 175 } 176 } 177 178 // Retrieve the current progress in the global space. May be invalid. 179 [[nodiscard]] ProportionValue GetGlobalProgress() const { 180 return mGlobalProgressOrNull ? mGlobalProgressOrNull->Progress() 181 : ProportionValue::MakeInvalid(); 182 } 183 184 // Retrieve the last known global location. May be null. 185 [[nodiscard]] const char* GetLastGlobalLocation() const { 186 return mGlobalProgressOrNull ? mGlobalProgressOrNull->LastLocation() 187 : nullptr; 188 } 189 190 // Set the current progress in the local space. 191 void SetLocalProgress(ProportionValue aLocalProgress, 192 const char* aLocationOrNullEmptyToIgnore) { 193 MOZ_ASSERT(!IsMovedFrom()); 194 if (mGlobalProgressOrNull && !mLocalToGlobalMultiplier.IsExactlyZero()) { 195 mGlobalProgressOrNull->SetProgress(LocalToGlobal(aLocalProgress), 196 aLocationOrNullEmptyToIgnore); 197 #ifdef DEBUG_PROCESSLOGGER 198 printf("[%d] - local %.0f%% ~ global %.2f%% \"%s\"\n", 199 int(baseprofiler::profiler_current_process_id().ToNumber()), 200 aLocalProgress.ToDouble() * 100.0, 201 LocalToGlobal(aLocalProgress).ToDouble() * 100.0, 202 aLocationOrNullEmptyToIgnore ? aLocationOrNullEmptyToIgnore 203 : "<null>"); 204 #endif // DEBUG_PROCESSLOGGER 205 } 206 } 207 208 // Create a sub-logger that will record progress in the given local range. 209 // E.g.: `f(pl.CreateSubLoggerFromTo(0.2, "f...", 0.4, "f done"));` expects 210 // that `f` will produce work in the local range 0.2 (when starting) to 0.4 211 // (when returning); `f` itself will update this provided logger from 0.0 212 // to 1.0 (local to that `f` function), which will effectively be converted to 213 // 0.2-0.4 (local to the calling function). 214 // This can cascade multiple levels, each deeper level affecting a smaller and 215 // smaller range in the global output. 216 [[nodiscard]] ProgressLogger CreateSubLoggerFromTo( 217 ProportionValue aSubStartInLocalSpace, 218 const char* aLocationOrNullEmptyToIgnoreAtStart, 219 ProportionValue aSubEndInLocalSpace, 220 const char* aLocationOrNullEmptyToIgnoreAtEnd = NO_LOCATION_UPDATE) { 221 MOZ_ASSERT(!IsMovedFrom()); 222 if (!mGlobalProgressOrNull) { 223 return ProgressLogger{}; 224 } 225 const ProportionValue subStartInGlobalSpace = 226 LocalToGlobal(aSubStartInLocalSpace); 227 const ProportionValue subEndInGlobalSpace = 228 LocalToGlobal(aSubEndInLocalSpace); 229 if (subStartInGlobalSpace.IsInvalid() || subEndInGlobalSpace.IsInvalid()) { 230 return ProgressLogger{mGlobalProgressOrNull, 231 /* Start */ ProportionValue::MakeInvalid(), 232 /* Multiplier */ ProportionValue{0.0}, 233 aLocationOrNullEmptyToIgnoreAtStart, 234 aLocationOrNullEmptyToIgnoreAtEnd}; 235 } 236 #ifdef DEBUG_PROCESSLOGGER 237 if (mGlobalProgressOrNull) { 238 printf("[%d] * Sub: local [%.0f%%, %.0f%%] ~ global [%.2f%%, %.2f%%]\n", 239 int(baseprofiler::profiler_current_process_id().ToNumber()), 240 aSubStartInLocalSpace.ToDouble() * 100.0, 241 aSubEndInLocalSpace.ToDouble() * 100.0, 242 subStartInGlobalSpace.ToDouble() * 100.0, 243 subEndInGlobalSpace.ToDouble() * 100.0); 244 } 245 #endif // DEBUG_PROCESSLOGGER 246 return ProgressLogger{ 247 mGlobalProgressOrNull, 248 /* Start */ subStartInGlobalSpace, 249 /* Multipler */ subEndInGlobalSpace - subStartInGlobalSpace, 250 aLocationOrNullEmptyToIgnoreAtStart, aLocationOrNullEmptyToIgnoreAtEnd}; 251 } 252 253 // Helper with no start location. 254 [[nodiscard]] ProgressLogger CreateSubLoggerFromTo( 255 ProportionValue aSubStartInLocalSpace, 256 ProportionValue aSubEndInLocalSpace, 257 const char* aLocationOrNullEmptyToIgnoreAtEnd = NO_LOCATION_UPDATE) { 258 return CreateSubLoggerFromTo(aSubStartInLocalSpace, NO_LOCATION_UPDATE, 259 aSubEndInLocalSpace, 260 aLocationOrNullEmptyToIgnoreAtEnd); 261 } 262 263 // Helper using the current progress as start. 264 [[nodiscard]] ProgressLogger CreateSubLoggerTo( 265 const char* aLocationOrNullEmptyToIgnoreAtStart, 266 ProportionValue aSubEndInLocalSpace, 267 const char* aLocationOrNullEmptyToIgnoreAtEnd = NO_LOCATION_UPDATE) { 268 MOZ_ASSERT(!IsMovedFrom()); 269 if (!mGlobalProgressOrNull) { 270 return ProgressLogger{}; 271 } 272 const ProportionValue subStartInGlobalSpace = GetGlobalProgress(); 273 const ProportionValue subEndInGlobalSpace = 274 LocalToGlobal(aSubEndInLocalSpace); 275 if (subStartInGlobalSpace.IsInvalid() || subEndInGlobalSpace.IsInvalid()) { 276 return ProgressLogger{mGlobalProgressOrNull, 277 /* Start */ ProportionValue::MakeInvalid(), 278 /* Multiplier */ ProportionValue{0.0}, 279 aLocationOrNullEmptyToIgnoreAtStart, 280 aLocationOrNullEmptyToIgnoreAtEnd}; 281 } 282 #ifdef DEBUG_PROCESSLOGGER 283 if (mGlobalProgressOrNull) { 284 printf("[%d] * Sub: local [(here), %.0f%%] ~ global [%.2f%%, %.2f%%]\n", 285 int(baseprofiler::profiler_current_process_id().ToNumber()), 286 aSubEndInLocalSpace.ToDouble() * 100.0, 287 subStartInGlobalSpace.ToDouble() * 100.0, 288 subEndInGlobalSpace.ToDouble() * 100.0); 289 } 290 #endif // DEBUG_PROCESSLOGGER 291 return ProgressLogger{ 292 mGlobalProgressOrNull, 293 /* Start */ subStartInGlobalSpace, 294 /* Multiplier */ subEndInGlobalSpace - subStartInGlobalSpace, 295 aLocationOrNullEmptyToIgnoreAtStart, aLocationOrNullEmptyToIgnoreAtEnd}; 296 } 297 298 // Helper using the current progress as start, no start location. 299 [[nodiscard]] ProgressLogger CreateSubLoggerTo( 300 ProportionValue aSubEndInLocalSpace, 301 const char* aLocationOrNullEmptyToIgnoreAtEnd = NO_LOCATION_UPDATE) { 302 return CreateSubLoggerTo(NO_LOCATION_UPDATE, aSubEndInLocalSpace, 303 aLocationOrNullEmptyToIgnoreAtEnd); 304 } 305 306 class IndexAndProgressLoggerRange; 307 308 [[nodiscard]] inline IndexAndProgressLoggerRange CreateLoopSubLoggersFromTo( 309 ProportionValue aLoopStartInLocalSpace, 310 ProportionValue aLoopEndInLocalSpace, uint32_t aLoopCount, 311 const char* aLocationOrNullEmptyToIgnoreAtEdges = 312 ProgressLogger::NO_LOCATION_UPDATE); 313 [[nodiscard]] inline IndexAndProgressLoggerRange CreateLoopSubLoggersTo( 314 ProportionValue aLoopEndInLocalSpace, uint32_t aLoopCount, 315 const char* aLocationOrNullEmptyToIgnoreAtEdges = 316 ProgressLogger::NO_LOCATION_UPDATE); 317 318 private: 319 // All constructions start at the local 0%. 320 ProgressLogger(RefPtr<SharedProgress> aGlobalProgressOrNull, 321 ProportionValue aLocalStartInGlobalSpace, 322 ProportionValue aLocalToGlobalMultiplier, 323 const char* aLocationOrNullEmptyToIgnoreAtConstruction, 324 const char* aLocationOrNullEmptyToIgnoreAtDestruction) 325 : mGlobalProgressOrNull(std::move(aGlobalProgressOrNull)), 326 mLocalStartInGlobalSpace(aLocalStartInGlobalSpace), 327 mLocalToGlobalMultiplier(aLocalToGlobalMultiplier), 328 mLocationAtDestruction(aLocationOrNullEmptyToIgnoreAtDestruction) { 329 MOZ_ASSERT(!IsMovedFrom(), "Don't construct a moved-from object!"); 330 SetLocalProgress(ProportionValue{0.0}, 331 aLocationOrNullEmptyToIgnoreAtConstruction); 332 } 333 334 void MarkMovedFrom() { 335 mLocalToGlobalMultiplier = ProportionValue::MakeInvalid(); 336 } 337 [[nodiscard]] bool IsMovedFrom() const { 338 return mLocalToGlobalMultiplier.IsInvalid(); 339 } 340 341 [[nodiscard]] ProportionValue LocalToGlobal( 342 ProportionValue aLocalProgress) const { 343 return aLocalProgress * mLocalToGlobalMultiplier + mLocalStartInGlobalSpace; 344 } 345 346 // Global progress value to update from local changes. 347 RefPtr<SharedProgress> mGlobalProgressOrNull; 348 349 // How much to multiply and add to a local [0, 100%] value, to get the 350 // corresponding value in the global space. 351 // If mLocalToGlobalMultiplier is invalid, this ProgressLogger is moved-from, 352 // functions should not be used, and destructor won't update progress. 353 ProportionValue mLocalStartInGlobalSpace; 354 ProportionValue mLocalToGlobalMultiplier; 355 356 const char* mLocationAtDestruction = nullptr; 357 }; 358 359 // Helper class for range-for loop, e.g., with `aProgressLogger`: 360 // for (auto [index, loopProgressLogger] : 361 // IndexAndProgressLoggerRange{aProgressLogger, 30_pc, 50_pc, 10, 362 // "looping..."}) { 363 // // This will loop 10 times. 364 // // `index` is the loop index, from 0 to 9. 365 // // The overall loop will start at 30% and end at 50% of aProgressLogger. 366 // // `loopProgressLogger` is the progress logger for each iteration, 367 // // covering 1/10th of the range, therefore: [30%,32%], then [32%,34%], 368 // // etc. until [48%,50%]. 369 // // Progress is automatically updated before/after each loop. 370 // } 371 // Note that this implementation is single-threaded, it does not support logging 372 // progress from parallel loops. 373 class ProgressLogger::IndexAndProgressLoggerRange { 374 public: 375 struct IndexAndProgressLogger { 376 uint32_t index; 377 ProgressLogger progressLogger; 378 }; 379 380 class IndexAndProgressLoggerEndIterator { 381 public: 382 explicit IndexAndProgressLoggerEndIterator(uint32_t aIndex) 383 : mIndex(aIndex) {} 384 385 [[nodiscard]] uint32_t Index() const { return mIndex; } 386 387 private: 388 uint32_t mIndex; 389 }; 390 391 class IndexAndProgressLoggerIterator { 392 public: 393 IndexAndProgressLoggerIterator( 394 RefPtr<ProgressLogger::SharedProgress> aGlobalProgressOrNull, 395 ProportionValue aLoopStartInGlobalSpace, 396 ProportionValue aLoopIncrementInGlobalSpace, 397 const char* aLocationOrNullEmptyToIgnoreAtEdges) 398 : mGlobalProgressOrNull(aGlobalProgressOrNull), 399 mLoopStartInGlobalSpace(aLoopStartInGlobalSpace), 400 mLoopIncrementInGlobalSpace(aLoopIncrementInGlobalSpace), 401 mIndex(0u), 402 mLocationOrNullEmptyToIgnoreAtEdges( 403 aLocationOrNullEmptyToIgnoreAtEdges) { 404 if (mGlobalProgressOrNull) { 405 mGlobalProgressOrNull->SetProgress(mLoopStartInGlobalSpace, 406 mLocationOrNullEmptyToIgnoreAtEdges); 407 } 408 } 409 410 [[nodiscard]] IndexAndProgressLogger operator*() { 411 return IndexAndProgressLogger{ 412 mIndex, 413 mGlobalProgressOrNull 414 ? ProgressLogger{mGlobalProgressOrNull, mLoopStartInGlobalSpace, 415 mLoopIncrementInGlobalSpace, 416 ProgressLogger::NO_LOCATION_UPDATE, 417 ProgressLogger::NO_LOCATION_UPDATE} 418 : ProgressLogger{}}; 419 } 420 421 [[nodiscard]] bool operator!=( 422 const IndexAndProgressLoggerEndIterator& aEnd) const { 423 return mIndex != aEnd.Index(); 424 } 425 426 IndexAndProgressLoggerIterator& operator++() { 427 ++mIndex; 428 mLoopStartInGlobalSpace = 429 mLoopStartInGlobalSpace + mLoopIncrementInGlobalSpace; 430 if (mGlobalProgressOrNull) { 431 mGlobalProgressOrNull->SetProgress(mLoopStartInGlobalSpace, 432 mLocationOrNullEmptyToIgnoreAtEdges); 433 } 434 return *this; 435 } 436 437 private: 438 RefPtr<ProgressLogger::SharedProgress> mGlobalProgressOrNull; 439 ProportionValue mLoopStartInGlobalSpace; 440 ProportionValue mLoopIncrementInGlobalSpace; 441 uint32_t mIndex; 442 const char* mLocationOrNullEmptyToIgnoreAtEdges; 443 }; 444 445 [[nodiscard]] IndexAndProgressLoggerIterator begin() { 446 return IndexAndProgressLoggerIterator{ 447 mGlobalProgressOrNull, mLoopStartInGlobalSpace, 448 mLoopIncrementInGlobalSpace, mLocationOrNullEmptyToIgnoreAtEdges}; 449 } 450 451 [[nodiscard]] IndexAndProgressLoggerEndIterator end() { 452 return IndexAndProgressLoggerEndIterator{mLoopCount}; 453 } 454 455 private: 456 friend class ProgressLogger; 457 IndexAndProgressLoggerRange(ProgressLogger& aProgressLogger, 458 ProportionValue aLoopStartInGlobalSpace, 459 ProportionValue aLoopEndInGlobalSpace, 460 uint32_t aLoopCount, 461 const char* aLocationOrNullEmptyToIgnoreAtEdges = 462 ProgressLogger::NO_LOCATION_UPDATE) 463 : mGlobalProgressOrNull(aProgressLogger.mGlobalProgressOrNull), 464 mLoopStartInGlobalSpace(aLoopStartInGlobalSpace), 465 mLoopIncrementInGlobalSpace( 466 (aLoopEndInGlobalSpace - aLoopStartInGlobalSpace) / aLoopCount), 467 mLoopCount(aLoopCount), 468 mLocationOrNullEmptyToIgnoreAtEdges( 469 aLocationOrNullEmptyToIgnoreAtEdges) {} 470 471 RefPtr<ProgressLogger::SharedProgress> mGlobalProgressOrNull; 472 ProportionValue mLoopStartInGlobalSpace; 473 ProportionValue mLoopIncrementInGlobalSpace; 474 uint32_t mLoopCount; 475 const char* mLocationOrNullEmptyToIgnoreAtEdges; 476 }; 477 478 [[nodiscard]] ProgressLogger::IndexAndProgressLoggerRange 479 ProgressLogger::CreateLoopSubLoggersFromTo( 480 ProportionValue aLoopStartInLocalSpace, 481 ProportionValue aLoopEndInLocalSpace, uint32_t aLoopCount, 482 const char* aLocationOrNullEmptyToIgnoreAtEdges) { 483 return IndexAndProgressLoggerRange{ 484 *this, LocalToGlobal(aLoopStartInLocalSpace), 485 LocalToGlobal(aLoopEndInLocalSpace), aLoopCount, 486 aLocationOrNullEmptyToIgnoreAtEdges}; 487 } 488 489 [[nodiscard]] ProgressLogger::IndexAndProgressLoggerRange 490 ProgressLogger::CreateLoopSubLoggersTo( 491 ProportionValue aLoopEndInLocalSpace, uint32_t aLoopCount, 492 const char* aLocationOrNullEmptyToIgnoreAtEdges) { 493 return IndexAndProgressLoggerRange{ 494 *this, GetGlobalProgress(), LocalToGlobal(aLoopEndInLocalSpace), 495 aLoopCount, aLocationOrNullEmptyToIgnoreAtEdges}; 496 } 497 498 } // namespace mozilla 499 500 #endif // ProgressLogger_h