TestBaseProfiler.cpp (193259B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/Attributes.h" 8 #include "mozilla/BaseAndGeckoProfilerDetail.h" 9 #include "mozilla/BaseProfileJSONWriter.h" 10 #include "mozilla/BaseProfiler.h" 11 #include "mozilla/BaseProfilerDetail.h" 12 #include "mozilla/FailureLatch.h" 13 #include "mozilla/NotNull.h" 14 #include "mozilla/ProgressLogger.h" 15 #include "mozilla/ProportionValue.h" 16 17 #ifdef MOZ_GECKO_PROFILER 18 # include "mozilla/BaseProfilerMarkerTypes.h" 19 # include "mozilla/leb128iterator.h" 20 # include "mozilla/ModuloBuffer.h" 21 # include "mozilla/mozalloc.h" 22 # include "mozilla/PowerOfTwo.h" 23 # include "mozilla/ProfileBufferChunk.h" 24 # include "mozilla/ProfileBufferChunkManagerSingle.h" 25 # include "mozilla/ProfileBufferChunkManagerWithLocalLimit.h" 26 # include "mozilla/ProfileBufferControlledChunkManager.h" 27 # include "mozilla/ProfileChunkedBuffer.h" 28 # include "mozilla/Vector.h" 29 #endif // MOZ_GECKO_PROFILER 30 31 #if defined(_MSC_VER) || defined(__MINGW32__) 32 # include <windows.h> 33 # include <mmsystem.h> 34 # include <process.h> 35 #else 36 # include <errno.h> 37 # include <time.h> 38 #endif 39 40 #include <algorithm> 41 #include <atomic> 42 #include <iostream> 43 #include <random> 44 #include <thread> 45 #include <type_traits> 46 #include <utility> 47 48 void TestFailureLatch() { 49 printf("TestFailureLatch...\n"); 50 51 // Test infallible latch. 52 { 53 mozilla::FailureLatchInfallibleSource& infallibleLatch = 54 mozilla::FailureLatchInfallibleSource::Singleton(); 55 56 MOZ_RELEASE_ASSERT(!infallibleLatch.Fallible()); 57 MOZ_RELEASE_ASSERT(!infallibleLatch.Failed()); 58 MOZ_RELEASE_ASSERT(!infallibleLatch.GetFailure()); 59 MOZ_RELEASE_ASSERT(&infallibleLatch.SourceFailureLatch() == 60 &mozilla::FailureLatchInfallibleSource::Singleton()); 61 MOZ_RELEASE_ASSERT(&std::as_const(infallibleLatch).SourceFailureLatch() == 62 &mozilla::FailureLatchInfallibleSource::Singleton()); 63 } 64 65 // Test failure latch basic functions. 66 { 67 mozilla::FailureLatchSource failureLatch; 68 69 MOZ_RELEASE_ASSERT(failureLatch.Fallible()); 70 MOZ_RELEASE_ASSERT(!failureLatch.Failed()); 71 MOZ_RELEASE_ASSERT(!failureLatch.GetFailure()); 72 MOZ_RELEASE_ASSERT(&failureLatch.SourceFailureLatch() == &failureLatch); 73 MOZ_RELEASE_ASSERT(&std::as_const(failureLatch).SourceFailureLatch() == 74 &failureLatch); 75 76 failureLatch.SetFailure("error"); 77 78 MOZ_RELEASE_ASSERT(failureLatch.Fallible()); 79 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 80 MOZ_RELEASE_ASSERT(failureLatch.GetFailure()); 81 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0); 82 83 failureLatch.SetFailure("later error"); 84 85 MOZ_RELEASE_ASSERT(failureLatch.Fallible()); 86 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 87 MOZ_RELEASE_ASSERT(failureLatch.GetFailure()); 88 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0); 89 } 90 91 // Test SetFailureFrom. 92 { 93 mozilla::FailureLatchSource failureLatch; 94 95 MOZ_RELEASE_ASSERT(!failureLatch.Failed()); 96 failureLatch.SetFailureFrom(failureLatch); 97 MOZ_RELEASE_ASSERT(!failureLatch.Failed()); 98 MOZ_RELEASE_ASSERT(!failureLatch.GetFailure()); 99 100 // SetFailureFrom with no error. 101 { 102 mozilla::FailureLatchSource failureLatchInnerOk; 103 MOZ_RELEASE_ASSERT(!failureLatchInnerOk.Failed()); 104 MOZ_RELEASE_ASSERT(!failureLatchInnerOk.GetFailure()); 105 106 MOZ_RELEASE_ASSERT(!failureLatch.Failed()); 107 failureLatch.SetFailureFrom(failureLatchInnerOk); 108 MOZ_RELEASE_ASSERT(!failureLatch.Failed()); 109 110 MOZ_RELEASE_ASSERT(!failureLatchInnerOk.Failed()); 111 MOZ_RELEASE_ASSERT(!failureLatchInnerOk.GetFailure()); 112 } 113 MOZ_RELEASE_ASSERT(!failureLatch.Failed()); 114 MOZ_RELEASE_ASSERT(!failureLatch.GetFailure()); 115 116 // SetFailureFrom with error. 117 { 118 mozilla::FailureLatchSource failureLatchInnerError; 119 MOZ_RELEASE_ASSERT(!failureLatchInnerError.Failed()); 120 MOZ_RELEASE_ASSERT(!failureLatchInnerError.GetFailure()); 121 122 failureLatchInnerError.SetFailure("inner error"); 123 MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed()); 124 MOZ_RELEASE_ASSERT( 125 strcmp(failureLatchInnerError.GetFailure(), "inner error") == 0); 126 127 MOZ_RELEASE_ASSERT(!failureLatch.Failed()); 128 failureLatch.SetFailureFrom(failureLatchInnerError); 129 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 130 131 MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed()); 132 MOZ_RELEASE_ASSERT( 133 strcmp(failureLatchInnerError.GetFailure(), "inner error") == 0); 134 } 135 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 136 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0); 137 138 failureLatch.SetFailureFrom(failureLatch); 139 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 140 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0); 141 142 // SetFailureFrom with error again, ignored. 143 { 144 mozilla::FailureLatchSource failureLatchInnerError; 145 failureLatchInnerError.SetFailure("later inner error"); 146 MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed()); 147 MOZ_RELEASE_ASSERT(strcmp(failureLatchInnerError.GetFailure(), 148 "later inner error") == 0); 149 150 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 151 failureLatch.SetFailureFrom(failureLatchInnerError); 152 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 153 154 MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed()); 155 MOZ_RELEASE_ASSERT(strcmp(failureLatchInnerError.GetFailure(), 156 "later inner error") == 0); 157 } 158 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 159 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0); 160 } 161 162 // Test FAILURELATCH_IMPL_PROXY 163 { 164 class Proxy final : public mozilla::FailureLatch { 165 public: 166 explicit Proxy(mozilla::FailureLatch& aFailureLatch) 167 : mFailureLatch(WrapNotNull(&aFailureLatch)) {} 168 169 void Set(mozilla::FailureLatch& aFailureLatch) { 170 mFailureLatch = WrapNotNull(&aFailureLatch); 171 } 172 173 FAILURELATCH_IMPL_PROXY(*mFailureLatch) 174 175 private: 176 mozilla::NotNull<mozilla::FailureLatch*> mFailureLatch; 177 }; 178 179 Proxy proxy{mozilla::FailureLatchInfallibleSource::Singleton()}; 180 181 MOZ_RELEASE_ASSERT(!proxy.Fallible()); 182 MOZ_RELEASE_ASSERT(!proxy.Failed()); 183 MOZ_RELEASE_ASSERT(!proxy.GetFailure()); 184 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == 185 &mozilla::FailureLatchInfallibleSource::Singleton()); 186 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() == 187 &mozilla::FailureLatchInfallibleSource::Singleton()); 188 189 // Error from proxy. 190 { 191 mozilla::FailureLatchSource failureLatch; 192 proxy.Set(failureLatch); 193 MOZ_RELEASE_ASSERT(proxy.Fallible()); 194 MOZ_RELEASE_ASSERT(!proxy.Failed()); 195 MOZ_RELEASE_ASSERT(!proxy.GetFailure()); 196 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch); 197 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() == 198 &failureLatch); 199 200 proxy.SetFailure("error"); 201 MOZ_RELEASE_ASSERT(proxy.Failed()); 202 MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0); 203 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 204 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0); 205 206 // Don't forget to stop pointing at soon-to-be-destroyed object. 207 proxy.Set(mozilla::FailureLatchInfallibleSource::Singleton()); 208 } 209 210 // Error from proxy's origin. 211 { 212 mozilla::FailureLatchSource failureLatch; 213 proxy.Set(failureLatch); 214 MOZ_RELEASE_ASSERT(proxy.Fallible()); 215 MOZ_RELEASE_ASSERT(!proxy.Failed()); 216 MOZ_RELEASE_ASSERT(!proxy.GetFailure()); 217 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch); 218 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() == 219 &failureLatch); 220 221 failureLatch.SetFailure("error"); 222 MOZ_RELEASE_ASSERT(proxy.Failed()); 223 MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0); 224 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 225 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0); 226 227 // Don't forget to stop pointing at soon-to-be-destroyed object. 228 proxy.Set(mozilla::FailureLatchInfallibleSource::Singleton()); 229 } 230 231 MOZ_RELEASE_ASSERT(!proxy.Fallible()); 232 MOZ_RELEASE_ASSERT(!proxy.Failed()); 233 MOZ_RELEASE_ASSERT(!proxy.GetFailure()); 234 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == 235 &mozilla::FailureLatchInfallibleSource::Singleton()); 236 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() == 237 &mozilla::FailureLatchInfallibleSource::Singleton()); 238 } 239 240 // Test FAILURELATCH_IMPL_PROXY_OR_INFALLIBLE 241 { 242 class ProxyOrNull final : public mozilla::FailureLatch { 243 public: 244 ProxyOrNull() = default; 245 246 void Set(mozilla::FailureLatch* aFailureLatchOrNull) { 247 mFailureLatchOrNull = aFailureLatchOrNull; 248 } 249 250 FAILURELATCH_IMPL_PROXY_OR_INFALLIBLE(mFailureLatchOrNull, ProxyOrNull) 251 252 private: 253 mozilla::FailureLatch* mFailureLatchOrNull = nullptr; 254 }; 255 256 ProxyOrNull proxy; 257 258 MOZ_RELEASE_ASSERT(!proxy.Fallible()); 259 MOZ_RELEASE_ASSERT(!proxy.Failed()); 260 MOZ_RELEASE_ASSERT(!proxy.GetFailure()); 261 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == 262 &mozilla::FailureLatchInfallibleSource::Singleton()); 263 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() == 264 &mozilla::FailureLatchInfallibleSource::Singleton()); 265 266 // Error from proxy. 267 { 268 mozilla::FailureLatchSource failureLatch; 269 proxy.Set(&failureLatch); 270 MOZ_RELEASE_ASSERT(proxy.Fallible()); 271 MOZ_RELEASE_ASSERT(!proxy.Failed()); 272 MOZ_RELEASE_ASSERT(!proxy.GetFailure()); 273 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch); 274 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() == 275 &failureLatch); 276 277 proxy.SetFailure("error"); 278 MOZ_RELEASE_ASSERT(proxy.Failed()); 279 MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0); 280 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 281 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0); 282 283 // Don't forget to stop pointing at soon-to-be-destroyed object. 284 proxy.Set(nullptr); 285 } 286 287 // Error from proxy's origin. 288 { 289 mozilla::FailureLatchSource failureLatch; 290 proxy.Set(&failureLatch); 291 MOZ_RELEASE_ASSERT(proxy.Fallible()); 292 MOZ_RELEASE_ASSERT(!proxy.Failed()); 293 MOZ_RELEASE_ASSERT(!proxy.GetFailure()); 294 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch); 295 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() == 296 &failureLatch); 297 298 failureLatch.SetFailure("error"); 299 MOZ_RELEASE_ASSERT(proxy.Failed()); 300 MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0); 301 MOZ_RELEASE_ASSERT(failureLatch.Failed()); 302 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0); 303 304 // Don't forget to stop pointing at soon-to-be-destroyed object. 305 proxy.Set(nullptr); 306 } 307 308 MOZ_RELEASE_ASSERT(!proxy.Fallible()); 309 MOZ_RELEASE_ASSERT(!proxy.Failed()); 310 MOZ_RELEASE_ASSERT(!proxy.GetFailure()); 311 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == 312 &mozilla::FailureLatchInfallibleSource::Singleton()); 313 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() == 314 &mozilla::FailureLatchInfallibleSource::Singleton()); 315 } 316 317 printf("TestFailureLatch done\n"); 318 } 319 320 void TestProfilerUtils() { 321 printf("TestProfilerUtils...\n"); 322 323 { 324 using mozilla::baseprofiler::BaseProfilerProcessId; 325 using Number = BaseProfilerProcessId::NumberType; 326 static constexpr Number scMaxNumber = std::numeric_limits<Number>::max(); 327 328 static_assert( 329 BaseProfilerProcessId{}.ToNumber() == 0, 330 "These tests assume that the unspecified process id number is 0; " 331 "if this fails, please update these tests accordingly"); 332 333 static_assert(!BaseProfilerProcessId{}.IsSpecified()); 334 static_assert(!BaseProfilerProcessId::FromNumber(0).IsSpecified()); 335 static_assert(BaseProfilerProcessId::FromNumber(1).IsSpecified()); 336 static_assert(BaseProfilerProcessId::FromNumber(123).IsSpecified()); 337 static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber).IsSpecified()); 338 339 static_assert(BaseProfilerProcessId::FromNumber(Number(1)).ToNumber() == 340 Number(1)); 341 static_assert(BaseProfilerProcessId::FromNumber(Number(123)).ToNumber() == 342 Number(123)); 343 static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber).ToNumber() == 344 scMaxNumber); 345 346 static_assert(BaseProfilerProcessId{} == BaseProfilerProcessId{}); 347 static_assert(BaseProfilerProcessId::FromNumber(Number(123)) == 348 BaseProfilerProcessId::FromNumber(Number(123))); 349 static_assert(BaseProfilerProcessId{} != 350 BaseProfilerProcessId::FromNumber(Number(123))); 351 static_assert(BaseProfilerProcessId::FromNumber(Number(123)) != 352 BaseProfilerProcessId{}); 353 static_assert(BaseProfilerProcessId::FromNumber(Number(123)) != 354 BaseProfilerProcessId::FromNumber(scMaxNumber)); 355 static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber) != 356 BaseProfilerProcessId::FromNumber(Number(123))); 357 358 // Verify trivial-copyability by memcpy'ing to&from same-size storage. 359 static_assert(std::is_trivially_copyable_v<BaseProfilerProcessId>); 360 BaseProfilerProcessId pid; 361 MOZ_RELEASE_ASSERT(!pid.IsSpecified()); 362 Number pidStorage; 363 static_assert(sizeof(pidStorage) == sizeof(pid)); 364 // Copy from BaseProfilerProcessId to storage. Note: We cannot assume that 365 // this is equal to what ToNumber() gives us. All we can do is verify that 366 // copying from storage back to BaseProfilerProcessId works as expected. 367 std::memcpy(&pidStorage, &pid, sizeof(pidStorage)); 368 BaseProfilerProcessId pid2 = BaseProfilerProcessId::FromNumber(2); 369 MOZ_RELEASE_ASSERT(pid2.IsSpecified()); 370 std::memcpy(&pid2, &pidStorage, sizeof(pid)); 371 MOZ_RELEASE_ASSERT(!pid2.IsSpecified()); 372 373 pid = BaseProfilerProcessId::FromNumber(123); 374 std::memcpy(&pidStorage, &pid, sizeof(pidStorage)); 375 pid2 = BaseProfilerProcessId{}; 376 MOZ_RELEASE_ASSERT(!pid2.IsSpecified()); 377 std::memcpy(&pid2, &pidStorage, sizeof(pid)); 378 MOZ_RELEASE_ASSERT(pid2.IsSpecified()); 379 MOZ_RELEASE_ASSERT(pid2.ToNumber() == 123); 380 381 // No conversions to/from numbers. 382 static_assert(!std::is_constructible_v<BaseProfilerProcessId, Number>); 383 static_assert(!std::is_assignable_v<BaseProfilerProcessId, Number>); 384 static_assert(!std::is_constructible_v<Number, BaseProfilerProcessId>); 385 static_assert(!std::is_assignable_v<Number, BaseProfilerProcessId>); 386 387 static_assert( 388 std::is_same_v< 389 decltype(mozilla::baseprofiler::profiler_current_process_id()), 390 BaseProfilerProcessId>); 391 MOZ_RELEASE_ASSERT( 392 mozilla::baseprofiler::profiler_current_process_id().IsSpecified()); 393 } 394 395 { 396 mozilla::baseprofiler::profiler_init_main_thread_id(); 397 398 using mozilla::baseprofiler::BaseProfilerThreadId; 399 using Number = BaseProfilerThreadId::NumberType; 400 static constexpr Number scMaxNumber = std::numeric_limits<Number>::max(); 401 402 static_assert( 403 BaseProfilerThreadId{}.ToNumber() == 0, 404 "These tests assume that the unspecified thread id number is 0; " 405 "if this fails, please update these tests accordingly"); 406 407 static_assert(!BaseProfilerThreadId{}.IsSpecified()); 408 static_assert(!BaseProfilerThreadId::FromNumber(0).IsSpecified()); 409 static_assert(BaseProfilerThreadId::FromNumber(1).IsSpecified()); 410 static_assert(BaseProfilerThreadId::FromNumber(123).IsSpecified()); 411 static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber).IsSpecified()); 412 413 static_assert(BaseProfilerThreadId::FromNumber(Number(1)).ToNumber() == 414 Number(1)); 415 static_assert(BaseProfilerThreadId::FromNumber(Number(123)).ToNumber() == 416 Number(123)); 417 static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber).ToNumber() == 418 scMaxNumber); 419 420 static_assert(BaseProfilerThreadId{} == BaseProfilerThreadId{}); 421 static_assert(BaseProfilerThreadId::FromNumber(Number(123)) == 422 BaseProfilerThreadId::FromNumber(Number(123))); 423 static_assert(BaseProfilerThreadId{} != 424 BaseProfilerThreadId::FromNumber(Number(123))); 425 static_assert(BaseProfilerThreadId::FromNumber(Number(123)) != 426 BaseProfilerThreadId{}); 427 static_assert(BaseProfilerThreadId::FromNumber(Number(123)) != 428 BaseProfilerThreadId::FromNumber(scMaxNumber)); 429 static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber) != 430 BaseProfilerThreadId::FromNumber(Number(123))); 431 432 // Verify trivial-copyability by memcpy'ing to&from same-size storage. 433 static_assert(std::is_trivially_copyable_v<BaseProfilerThreadId>); 434 BaseProfilerThreadId tid; 435 MOZ_RELEASE_ASSERT(!tid.IsSpecified()); 436 Number tidStorage; 437 static_assert(sizeof(tidStorage) == sizeof(tid)); 438 // Copy from BaseProfilerThreadId to storage. Note: We cannot assume that 439 // this is equal to what ToNumber() gives us. All we can do is verify that 440 // copying from storage back to BaseProfilerThreadId works as expected. 441 std::memcpy(&tidStorage, &tid, sizeof(tidStorage)); 442 BaseProfilerThreadId tid2 = BaseProfilerThreadId::FromNumber(2); 443 MOZ_RELEASE_ASSERT(tid2.IsSpecified()); 444 std::memcpy(&tid2, &tidStorage, sizeof(tid)); 445 MOZ_RELEASE_ASSERT(!tid2.IsSpecified()); 446 447 tid = BaseProfilerThreadId::FromNumber(Number(123)); 448 std::memcpy(&tidStorage, &tid, sizeof(tidStorage)); 449 tid2 = BaseProfilerThreadId{}; 450 MOZ_RELEASE_ASSERT(!tid2.IsSpecified()); 451 std::memcpy(&tid2, &tidStorage, sizeof(tid)); 452 MOZ_RELEASE_ASSERT(tid2.IsSpecified()); 453 MOZ_RELEASE_ASSERT(tid2.ToNumber() == Number(123)); 454 455 // No conversions to/from numbers. 456 static_assert(!std::is_constructible_v<BaseProfilerThreadId, Number>); 457 static_assert(!std::is_assignable_v<BaseProfilerThreadId, Number>); 458 static_assert(!std::is_constructible_v<Number, BaseProfilerThreadId>); 459 static_assert(!std::is_assignable_v<Number, BaseProfilerThreadId>); 460 461 static_assert(std::is_same_v< 462 decltype(mozilla::baseprofiler::profiler_current_thread_id()), 463 BaseProfilerThreadId>); 464 BaseProfilerThreadId mainTestThreadId = 465 mozilla::baseprofiler::profiler_current_thread_id(); 466 MOZ_RELEASE_ASSERT(mainTestThreadId.IsSpecified()); 467 468 BaseProfilerThreadId mainThreadId = 469 mozilla::baseprofiler::profiler_main_thread_id(); 470 MOZ_RELEASE_ASSERT(mainThreadId.IsSpecified()); 471 472 MOZ_RELEASE_ASSERT(mainThreadId == mainTestThreadId, 473 "Test should run on the main thread"); 474 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::profiler_is_main_thread()); 475 476 std::thread testThread([&]() { 477 const BaseProfilerThreadId testThreadId = 478 mozilla::baseprofiler::profiler_current_thread_id(); 479 MOZ_RELEASE_ASSERT(testThreadId.IsSpecified()); 480 MOZ_RELEASE_ASSERT(testThreadId != mainThreadId); 481 MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_is_main_thread()); 482 }); 483 testThread.join(); 484 } 485 486 // No conversions between processes and threads. 487 static_assert( 488 !std::is_constructible_v<mozilla::baseprofiler::BaseProfilerThreadId, 489 mozilla::baseprofiler::BaseProfilerProcessId>); 490 static_assert( 491 !std::is_assignable_v<mozilla::baseprofiler::BaseProfilerThreadId, 492 mozilla::baseprofiler::BaseProfilerProcessId>); 493 static_assert( 494 !std::is_constructible_v<mozilla::baseprofiler::BaseProfilerProcessId, 495 mozilla::baseprofiler::BaseProfilerThreadId>); 496 static_assert( 497 !std::is_assignable_v<mozilla::baseprofiler::BaseProfilerProcessId, 498 mozilla::baseprofiler::BaseProfilerThreadId>); 499 500 printf("TestProfilerUtils done\n"); 501 } 502 503 void TestBaseAndProfilerDetail() { 504 printf("TestBaseAndProfilerDetail...\n"); 505 506 { 507 using mozilla::profiler::detail::FilterHasPid; 508 509 const auto pid123 = 510 mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(123); 511 MOZ_RELEASE_ASSERT(FilterHasPid("pid:123", pid123)); 512 MOZ_RELEASE_ASSERT(!FilterHasPid("", pid123)); 513 MOZ_RELEASE_ASSERT(!FilterHasPid(" ", pid123)); 514 MOZ_RELEASE_ASSERT(!FilterHasPid("123", pid123)); 515 MOZ_RELEASE_ASSERT(!FilterHasPid("pid", pid123)); 516 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:", pid123)); 517 MOZ_RELEASE_ASSERT(!FilterHasPid("pid=123", pid123)); 518 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:123 ", pid123)); 519 MOZ_RELEASE_ASSERT(!FilterHasPid("pid: 123", pid123)); 520 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0123", pid123)); 521 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0000000000000000000000123", pid123)); 522 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:12", pid123)); 523 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:1234", pid123)); 524 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0", pid123)); 525 526 using PidNumber = mozilla::baseprofiler::BaseProfilerProcessId::NumberType; 527 const PidNumber maxNumber = std::numeric_limits<PidNumber>::max(); 528 const auto maxPid = 529 mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(maxNumber); 530 const std::string maxPidString = "pid:" + std::to_string(maxNumber); 531 MOZ_RELEASE_ASSERT(FilterHasPid(maxPidString.c_str(), maxPid)); 532 533 const std::string tooBigPidString = maxPidString + "0"; 534 MOZ_RELEASE_ASSERT(!FilterHasPid(tooBigPidString.c_str(), maxPid)); 535 } 536 537 { 538 using mozilla::profiler::detail::FiltersExcludePid; 539 const auto pid123 = 540 mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(123); 541 542 MOZ_RELEASE_ASSERT( 543 !FiltersExcludePid(mozilla::Span<const char*>{}, pid123)); 544 545 { 546 const char* const filters[] = {"main"}; 547 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123)); 548 } 549 550 { 551 const char* const filters[] = {"main", "pid:123"}; 552 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123)); 553 } 554 555 { 556 const char* const filters[] = {"main", "pid:456"}; 557 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123)); 558 } 559 560 { 561 const char* const filters[] = {"pid:123"}; 562 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123)); 563 } 564 565 { 566 const char* const filters[] = {"pid:123", "pid:456"}; 567 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123)); 568 } 569 570 { 571 const char* const filters[] = {"pid:456", "pid:123"}; 572 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123)); 573 } 574 575 { 576 const char* const filters[] = {"pid:456"}; 577 MOZ_RELEASE_ASSERT(FiltersExcludePid(filters, pid123)); 578 } 579 580 { 581 const char* const filters[] = {"pid:456", "pid:789"}; 582 MOZ_RELEASE_ASSERT(FiltersExcludePid(filters, pid123)); 583 } 584 } 585 586 printf("TestBaseAndProfilerDetail done\n"); 587 } 588 589 void TestSharedMutex() { 590 printf("TestSharedMutex...\n"); 591 592 mozilla::baseprofiler::detail::BaseProfilerSharedMutex sm; 593 594 // First round of minimal tests in this thread. 595 596 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 597 598 sm.LockExclusive(); 599 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread()); 600 sm.UnlockExclusive(); 601 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 602 603 sm.LockShared(); 604 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 605 sm.UnlockShared(); 606 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 607 608 { 609 mozilla::baseprofiler::detail::BaseProfilerAutoLockExclusive exclusiveLock{ 610 sm}; 611 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread()); 612 } 613 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 614 615 { 616 mozilla::baseprofiler::detail::BaseProfilerAutoLockShared sharedLock{sm}; 617 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 618 } 619 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 620 621 // The following will run actions between two threads, to verify that 622 // exclusive and shared locks work as expected. 623 624 // These actions will happen from top to bottom. 625 // This will test all possible lock interactions. 626 enum NextAction { // State of the lock: 627 t1Starting, // (x=exclusive, s=shared, ?=blocked) 628 t2Starting, // t1 t2 629 t1LockExclusive, // x 630 t2LockExclusiveAndBlock, // x x? - Can't have two exclusives. 631 t1UnlockExclusive, // x 632 t2UnblockedAfterT1Unlock, // x 633 t1LockSharedAndBlock, // s? x - Can't have shared during excl 634 t2UnlockExclusive, // s 635 t1UnblockedAfterT2Unlock, // s 636 t2LockShared, // s s - Can have multiple shared locks 637 t1UnlockShared, // s 638 t2StillLockedShared, // s 639 t1LockExclusiveAndBlock, // x? s - Can't have excl during shared 640 t2UnlockShared, // x 641 t1UnblockedAfterT2UnlockShared, // x 642 t2CheckAfterT1Lock, // x 643 t1LastUnlockExclusive, // (unlocked) 644 done 645 }; 646 647 // Each thread will repeatedly read this `nextAction`, and run actions that 648 // target it... 649 std::atomic<NextAction> nextAction{static_cast<NextAction>(0)}; 650 // ... and advance to the next available action (which should usually be for 651 // the other thread). 652 auto AdvanceAction = [&nextAction]() { 653 MOZ_RELEASE_ASSERT(nextAction <= done); 654 nextAction = static_cast<NextAction>(static_cast<int>(nextAction) + 1); 655 }; 656 657 std::thread t1{[&]() { 658 for (;;) { 659 switch (nextAction) { 660 case t1Starting: 661 AdvanceAction(); 662 break; 663 case t1LockExclusive: 664 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 665 sm.LockExclusive(); 666 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread()); 667 AdvanceAction(); 668 break; 669 case t1UnlockExclusive: 670 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread()); 671 // Advance first, before unlocking, so that t2 sees the new state. 672 AdvanceAction(); 673 sm.UnlockExclusive(); 674 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 675 break; 676 case t1LockSharedAndBlock: 677 // Advance action before attempting to lock after t2's exclusive lock. 678 AdvanceAction(); 679 sm.LockShared(); 680 // We will only acquire the lock after t1 unlocks. 681 MOZ_RELEASE_ASSERT(nextAction == t1UnblockedAfterT2Unlock); 682 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 683 AdvanceAction(); 684 break; 685 case t1UnlockShared: 686 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 687 // Advance first, before unlocking, so that t2 sees the new state. 688 AdvanceAction(); 689 sm.UnlockShared(); 690 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 691 break; 692 case t1LockExclusiveAndBlock: 693 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 694 // Advance action before attempting to lock after t2's shared lock. 695 AdvanceAction(); 696 sm.LockExclusive(); 697 // We will only acquire the lock after t2 unlocks. 698 MOZ_RELEASE_ASSERT(nextAction == t1UnblockedAfterT2UnlockShared); 699 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread()); 700 AdvanceAction(); 701 break; 702 case t1LastUnlockExclusive: 703 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread()); 704 // Advance first, before unlocking, so that t2 sees the new state. 705 AdvanceAction(); 706 sm.UnlockExclusive(); 707 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 708 break; 709 case done: 710 return; 711 default: 712 // Ignore other actions intended for t2. 713 break; 714 } 715 } 716 }}; 717 718 std::thread t2{[&]() { 719 for (;;) { 720 switch (nextAction) { 721 case t2Starting: 722 AdvanceAction(); 723 break; 724 case t2LockExclusiveAndBlock: 725 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 726 // Advance action before attempting to lock after t1's exclusive lock. 727 AdvanceAction(); 728 sm.LockExclusive(); 729 // We will only acquire the lock after t1 unlocks. 730 MOZ_RELEASE_ASSERT(nextAction == t2UnblockedAfterT1Unlock); 731 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread()); 732 AdvanceAction(); 733 break; 734 case t2UnlockExclusive: 735 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread()); 736 // Advance first, before unlocking, so that t1 sees the new state. 737 AdvanceAction(); 738 sm.UnlockExclusive(); 739 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 740 break; 741 case t2LockShared: 742 sm.LockShared(); 743 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 744 AdvanceAction(); 745 break; 746 case t2StillLockedShared: 747 AdvanceAction(); 748 break; 749 case t2UnlockShared: 750 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 751 // Advance first, before unlocking, so that t1 sees the new state. 752 AdvanceAction(); 753 sm.UnlockShared(); 754 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 755 break; 756 case t2CheckAfterT1Lock: 757 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread()); 758 AdvanceAction(); 759 break; 760 case done: 761 return; 762 default: 763 // Ignore other actions intended for t1. 764 break; 765 } 766 } 767 }}; 768 769 t1.join(); 770 t2.join(); 771 772 printf("TestSharedMutex done\n"); 773 } 774 775 void TestProportionValue() { 776 printf("TestProportionValue...\n"); 777 778 using mozilla::ProportionValue; 779 780 #define STATIC_ASSERT_EQ(a, b) \ 781 static_assert((a) == (b)); \ 782 MOZ_RELEASE_ASSERT((a) == (b)); 783 784 #define STATIC_ASSERT(e) STATIC_ASSERT_EQ(e, true) 785 786 // Conversion from&to double. 787 STATIC_ASSERT_EQ(ProportionValue().ToDouble(), 0.0); 788 STATIC_ASSERT_EQ(ProportionValue(0.0).ToDouble(), 0.0); 789 STATIC_ASSERT_EQ(ProportionValue(0.5).ToDouble(), 0.5); 790 STATIC_ASSERT_EQ(ProportionValue(1.0).ToDouble(), 1.0); 791 792 // Clamping. 793 STATIC_ASSERT_EQ( 794 ProportionValue(std::numeric_limits<double>::min()).ToDouble(), 0.0); 795 STATIC_ASSERT_EQ( 796 ProportionValue(std::numeric_limits<long double>::min()).ToDouble(), 0.0); 797 STATIC_ASSERT_EQ(ProportionValue(-1.0).ToDouble(), 0.0); 798 STATIC_ASSERT_EQ(ProportionValue(-0.01).ToDouble(), 0.0); 799 STATIC_ASSERT_EQ(ProportionValue(-0.0).ToDouble(), 0.0); 800 STATIC_ASSERT_EQ(ProportionValue(1.01).ToDouble(), 1.0); 801 STATIC_ASSERT_EQ( 802 ProportionValue(std::numeric_limits<double>::max()).ToDouble(), 1.0); 803 804 // User-defined literal. 805 { 806 using namespace mozilla::literals::ProportionValue_literals; 807 STATIC_ASSERT_EQ(0_pc, ProportionValue(0.0)); 808 STATIC_ASSERT_EQ(0._pc, ProportionValue(0.0)); 809 STATIC_ASSERT_EQ(50_pc, ProportionValue(0.5)); 810 STATIC_ASSERT_EQ(50._pc, ProportionValue(0.5)); 811 STATIC_ASSERT_EQ(100_pc, ProportionValue(1.0)); 812 STATIC_ASSERT_EQ(100._pc, ProportionValue(1.0)); 813 STATIC_ASSERT_EQ(101_pc, ProportionValue(1.0)); 814 STATIC_ASSERT_EQ(100.01_pc, ProportionValue(1.0)); 815 STATIC_ASSERT_EQ(1000_pc, ProportionValue(1.0)); 816 STATIC_ASSERT_EQ(1000._pc, ProportionValue(1.0)); 817 } 818 { 819 // ProportionValue_literals is an inline namespace of mozilla::literals, so 820 // it's optional. 821 using namespace mozilla::literals; 822 STATIC_ASSERT_EQ(0_pc, ProportionValue(0.0)); 823 STATIC_ASSERT_EQ(0._pc, ProportionValue(0.0)); 824 STATIC_ASSERT_EQ(50_pc, ProportionValue(0.5)); 825 STATIC_ASSERT_EQ(50._pc, ProportionValue(0.5)); 826 STATIC_ASSERT_EQ(100_pc, ProportionValue(1.0)); 827 STATIC_ASSERT_EQ(100._pc, ProportionValue(1.0)); 828 STATIC_ASSERT_EQ(101_pc, ProportionValue(1.0)); 829 STATIC_ASSERT_EQ(100.01_pc, ProportionValue(1.0)); 830 STATIC_ASSERT_EQ(1000_pc, ProportionValue(1.0)); 831 STATIC_ASSERT_EQ(1000._pc, ProportionValue(1.0)); 832 } 833 834 // Invalid construction, conversion to double NaN. 835 MOZ_RELEASE_ASSERT(std::isnan(ProportionValue::MakeInvalid().ToDouble())); 836 837 using namespace mozilla::literals::ProportionValue_literals; 838 839 // Conversion to&from underlying integral number. 840 STATIC_ASSERT_EQ( 841 ProportionValue::FromUnderlyingType((0_pc).ToUnderlyingType()).ToDouble(), 842 0.0); 843 STATIC_ASSERT_EQ( 844 ProportionValue::FromUnderlyingType((50_pc).ToUnderlyingType()) 845 .ToDouble(), 846 0.5); 847 STATIC_ASSERT_EQ( 848 ProportionValue::FromUnderlyingType((100_pc).ToUnderlyingType()) 849 .ToDouble(), 850 1.0); 851 STATIC_ASSERT(ProportionValue::FromUnderlyingType( 852 ProportionValue::MakeInvalid().ToUnderlyingType()) 853 .IsInvalid()); 854 855 // IsExactlyZero. 856 STATIC_ASSERT(ProportionValue().IsExactlyZero()); 857 STATIC_ASSERT((0_pc).IsExactlyZero()); 858 STATIC_ASSERT(!(50_pc).IsExactlyZero()); 859 STATIC_ASSERT(!(100_pc).IsExactlyZero()); 860 STATIC_ASSERT(!ProportionValue::MakeInvalid().IsExactlyZero()); 861 862 // IsExactlyOne. 863 STATIC_ASSERT(!ProportionValue().IsExactlyOne()); 864 STATIC_ASSERT(!(0_pc).IsExactlyOne()); 865 STATIC_ASSERT(!(50_pc).IsExactlyOne()); 866 STATIC_ASSERT((100_pc).IsExactlyOne()); 867 STATIC_ASSERT(!ProportionValue::MakeInvalid().IsExactlyOne()); 868 869 // IsValid. 870 STATIC_ASSERT(ProportionValue().IsValid()); 871 STATIC_ASSERT((0_pc).IsValid()); 872 STATIC_ASSERT((50_pc).IsValid()); 873 STATIC_ASSERT((100_pc).IsValid()); 874 STATIC_ASSERT(!ProportionValue::MakeInvalid().IsValid()); 875 876 // IsInvalid. 877 STATIC_ASSERT(!ProportionValue().IsInvalid()); 878 STATIC_ASSERT(!(0_pc).IsInvalid()); 879 STATIC_ASSERT(!(50_pc).IsInvalid()); 880 STATIC_ASSERT(!(100_pc).IsInvalid()); 881 STATIC_ASSERT(ProportionValue::MakeInvalid().IsInvalid()); 882 883 // Addition. 884 STATIC_ASSERT_EQ((0_pc + 0_pc).ToDouble(), 0.0); 885 STATIC_ASSERT_EQ((0_pc + 100_pc).ToDouble(), 1.0); 886 STATIC_ASSERT_EQ((100_pc + 0_pc).ToDouble(), 1.0); 887 STATIC_ASSERT_EQ((100_pc + 100_pc).ToDouble(), 1.0); 888 STATIC_ASSERT((ProportionValue::MakeInvalid() + 50_pc).IsInvalid()); 889 STATIC_ASSERT((50_pc + ProportionValue::MakeInvalid()).IsInvalid()); 890 891 // Subtraction. 892 STATIC_ASSERT_EQ((0_pc - 0_pc).ToDouble(), 0.0); 893 STATIC_ASSERT_EQ((0_pc - 100_pc).ToDouble(), 0.0); 894 STATIC_ASSERT_EQ((100_pc - 0_pc).ToDouble(), 1.0); 895 STATIC_ASSERT_EQ((100_pc - 100_pc).ToDouble(), 0.0); 896 STATIC_ASSERT((ProportionValue::MakeInvalid() - 50_pc).IsInvalid()); 897 STATIC_ASSERT((50_pc - ProportionValue::MakeInvalid()).IsInvalid()); 898 899 // Multiplication. 900 STATIC_ASSERT_EQ((0_pc * 0_pc).ToDouble(), 0.0); 901 STATIC_ASSERT_EQ((0_pc * 100_pc).ToDouble(), 0.0); 902 STATIC_ASSERT_EQ((50_pc * 50_pc).ToDouble(), 0.25); 903 STATIC_ASSERT_EQ((50_pc * 100_pc).ToDouble(), 0.5); 904 STATIC_ASSERT_EQ((100_pc * 50_pc).ToDouble(), 0.5); 905 STATIC_ASSERT_EQ((100_pc * 0_pc).ToDouble(), 0.0); 906 STATIC_ASSERT_EQ((100_pc * 100_pc).ToDouble(), 1.0); 907 STATIC_ASSERT((ProportionValue::MakeInvalid() * 50_pc).IsInvalid()); 908 STATIC_ASSERT((50_pc * ProportionValue::MakeInvalid()).IsInvalid()); 909 910 // Division by a positive integer value. 911 STATIC_ASSERT_EQ((100_pc / 1u).ToDouble(), 1.0); 912 STATIC_ASSERT_EQ((100_pc / 2u).ToDouble(), 0.5); 913 STATIC_ASSERT_EQ( 914 (ProportionValue::FromUnderlyingType(6u) / 2u).ToUnderlyingType(), 3u); 915 STATIC_ASSERT_EQ( 916 (ProportionValue::FromUnderlyingType(5u) / 2u).ToUnderlyingType(), 2u); 917 STATIC_ASSERT_EQ( 918 (ProportionValue::FromUnderlyingType(1u) / 2u).ToUnderlyingType(), 0u); 919 STATIC_ASSERT_EQ( 920 (ProportionValue::FromUnderlyingType(0u) / 2u).ToUnderlyingType(), 0u); 921 STATIC_ASSERT((100_pc / 0u).IsInvalid()); 922 STATIC_ASSERT((ProportionValue::MakeInvalid() / 2u).IsInvalid()); 923 924 // Multiplication by a positive integer value. 925 STATIC_ASSERT_EQ((100_pc * 1u).ToDouble(), 1.0); 926 STATIC_ASSERT_EQ((50_pc * 1u).ToDouble(), 0.5); 927 STATIC_ASSERT_EQ((50_pc * 2u).ToDouble(), 1.0); 928 STATIC_ASSERT_EQ((50_pc * 3u).ToDouble(), 1.0); // Clamped. 929 STATIC_ASSERT_EQ( 930 (ProportionValue::FromUnderlyingType(1u) * 2u).ToUnderlyingType(), 2u); 931 STATIC_ASSERT((ProportionValue::MakeInvalid() * 2u).IsInvalid()); 932 933 // Verifying PV - u < (PV / u) * u <= PV, with n=3, PV between 6 and 9 : 934 STATIC_ASSERT_EQ( 935 (ProportionValue::FromUnderlyingType(6u) / 3u).ToUnderlyingType(), 2u); 936 STATIC_ASSERT_EQ( 937 (ProportionValue::FromUnderlyingType(7u) / 3u).ToUnderlyingType(), 2u); 938 STATIC_ASSERT_EQ( 939 (ProportionValue::FromUnderlyingType(8u) / 3u).ToUnderlyingType(), 2u); 940 STATIC_ASSERT_EQ( 941 (ProportionValue::FromUnderlyingType(9u) / 3u).ToUnderlyingType(), 3u); 942 943 // Direct comparisons. 944 STATIC_ASSERT_EQ(0_pc, 0_pc); 945 STATIC_ASSERT(0_pc == 0_pc); 946 STATIC_ASSERT(!(0_pc == 100_pc)); 947 STATIC_ASSERT(0_pc != 100_pc); 948 STATIC_ASSERT(!(0_pc != 0_pc)); 949 STATIC_ASSERT(0_pc < 100_pc); 950 STATIC_ASSERT(!(0_pc < 0_pc)); 951 STATIC_ASSERT(0_pc <= 0_pc); 952 STATIC_ASSERT(0_pc <= 100_pc); 953 STATIC_ASSERT(!(100_pc <= 0_pc)); 954 STATIC_ASSERT(100_pc > 0_pc); 955 STATIC_ASSERT(!(100_pc > 100_pc)); 956 STATIC_ASSERT(100_pc >= 0_pc); 957 STATIC_ASSERT(100_pc >= 100_pc); 958 STATIC_ASSERT(!(0_pc >= 100_pc)); 959 // 0.5 is binary-friendly, so we can double it and compare it exactly. 960 STATIC_ASSERT_EQ(50_pc + 50_pc, 100_pc); 961 962 #undef STATIC_ASSERT_EQ 963 964 printf("TestProportionValue done\n"); 965 } 966 967 template <typename Arg0, typename... Args> 968 bool AreAllEqual(Arg0&& aArg0, Args&&... aArgs) { 969 return ((aArg0 == aArgs) && ...); 970 } 971 972 void TestProgressLogger() { 973 printf("TestProgressLogger...\n"); 974 975 using mozilla::ProgressLogger; 976 using mozilla::ProportionValue; 977 using namespace mozilla::literals::ProportionValue_literals; 978 979 auto progressRefPtr = mozilla::MakeRefPtr<ProgressLogger::SharedProgress>(); 980 MOZ_RELEASE_ASSERT(progressRefPtr); 981 MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero()); 982 983 { 984 ProgressLogger pl(progressRefPtr, "Started", "All done"); 985 MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero()); 986 MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyZero()); 987 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(), 988 pl.GetLastGlobalLocation(), "Started")); 989 990 // At this top level, the scale is 1:1. 991 pl.SetLocalProgress(10_pc, "Top 10%"); 992 MOZ_RELEASE_ASSERT( 993 AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(), 10_pc)); 994 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(), 995 pl.GetLastGlobalLocation(), "Top 10%")); 996 997 pl.SetLocalProgress(0_pc, "Restarted"); 998 MOZ_RELEASE_ASSERT( 999 AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(), 0_pc)); 1000 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(), 1001 pl.GetLastGlobalLocation(), "Restarted")); 1002 1003 { 1004 // Create a sub-logger for the whole global range. Notice that this is 1005 // moving the current progress back to 0. 1006 ProgressLogger plSub1 = 1007 pl.CreateSubLoggerFromTo(0_pc, "Sub1 started", 100_pc, "Sub1 ended"); 1008 MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero()); 1009 MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyZero()); 1010 MOZ_RELEASE_ASSERT(plSub1.GetGlobalProgress().IsExactlyZero()); 1011 MOZ_RELEASE_ASSERT(AreAllEqual( 1012 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(), 1013 plSub1.GetLastGlobalLocation(), "Sub1 started")); 1014 1015 // At this level, the scale is still 1:1. 1016 plSub1.SetLocalProgress(10_pc, "Sub1 10%"); 1017 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(), 1018 pl.GetGlobalProgress(), 1019 plSub1.GetGlobalProgress(), 10_pc)); 1020 MOZ_RELEASE_ASSERT(AreAllEqual( 1021 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(), 1022 plSub1.GetLastGlobalLocation(), "Sub1 10%")); 1023 1024 { 1025 // Create a sub-logger half the global range. 1026 // 0 0.25 0.375 0.5 0.625 0.75 1 1027 // |---------------|-------|-------|-------|-------|---------------| 1028 // plSub2: 0 0.25 0.5 0.75 1 1029 ProgressLogger plSub2 = plSub1.CreateSubLoggerFromTo( 1030 25_pc, "Sub2 started", 75_pc, "Sub2 ended"); 1031 MOZ_RELEASE_ASSERT(AreAllEqual( 1032 progressRefPtr->Progress(), pl.GetGlobalProgress(), 1033 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 25_pc)); 1034 MOZ_RELEASE_ASSERT(AreAllEqual( 1035 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(), 1036 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(), 1037 "Sub2 started")); 1038 1039 plSub2.SetLocalProgress(25_pc, "Sub2 25%"); 1040 MOZ_RELEASE_ASSERT(AreAllEqual( 1041 progressRefPtr->Progress(), pl.GetGlobalProgress(), 1042 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 37.5_pc)); 1043 MOZ_RELEASE_ASSERT(AreAllEqual( 1044 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(), 1045 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(), 1046 "Sub2 25%")); 1047 1048 plSub2.SetLocalProgress(50_pc, "Sub2 50%"); 1049 MOZ_RELEASE_ASSERT(AreAllEqual( 1050 progressRefPtr->Progress(), pl.GetGlobalProgress(), 1051 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 50_pc)); 1052 MOZ_RELEASE_ASSERT(AreAllEqual( 1053 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(), 1054 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(), 1055 "Sub2 50%")); 1056 1057 { 1058 // Create a sub-logger half the parent range. 1059 // 0 0.25 0.375 0.5 0.625 0.75 1 1060 // |---------------|-------|-------|-------|-------|---------------| 1061 // plSub2: 0 0.25 0.5 0.75 1 1062 // plSub3: 0 0.5 1 1063 ProgressLogger plSub3 = plSub2.CreateSubLoggerTo( 1064 "Sub3 started", 100_pc, ProgressLogger::NO_LOCATION_UPDATE); 1065 MOZ_RELEASE_ASSERT(AreAllEqual( 1066 progressRefPtr->Progress(), pl.GetGlobalProgress(), 1067 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 1068 plSub3.GetGlobalProgress(), 50_pc)); 1069 MOZ_RELEASE_ASSERT(AreAllEqual( 1070 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(), 1071 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(), 1072 plSub3.GetLastGlobalLocation(), "Sub3 started")); 1073 1074 plSub3.SetLocalProgress(50_pc, "Sub3 50%"); 1075 MOZ_RELEASE_ASSERT(AreAllEqual( 1076 progressRefPtr->Progress(), pl.GetGlobalProgress(), 1077 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 1078 plSub3.GetGlobalProgress(), 62.5_pc)); 1079 MOZ_RELEASE_ASSERT(AreAllEqual( 1080 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(), 1081 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(), 1082 plSub3.GetLastGlobalLocation(), "Sub3 50%")); 1083 } // End of plSub3 1084 1085 // When plSub3 ends, progress moves to its 100%, which is also plSub2's 1086 // 100%, which is plSub1's and the global progress of 75% 1087 MOZ_RELEASE_ASSERT(AreAllEqual( 1088 progressRefPtr->Progress(), pl.GetGlobalProgress(), 1089 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 75_pc)); 1090 // But location is still at the last explicit update. 1091 MOZ_RELEASE_ASSERT(AreAllEqual( 1092 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(), 1093 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(), 1094 "Sub3 50%")); 1095 } // End of plSub2 1096 1097 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(), 1098 pl.GetGlobalProgress(), 1099 plSub1.GetGlobalProgress(), 75_pc)); 1100 MOZ_RELEASE_ASSERT(AreAllEqual( 1101 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(), 1102 plSub1.GetLastGlobalLocation(), "Sub2 ended")); 1103 } // End of plSub1 1104 1105 MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyOne()); 1106 MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyOne()); 1107 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(), 1108 pl.GetLastGlobalLocation(), "Sub1 ended")); 1109 1110 const auto loopStart = 75_pc; 1111 const auto loopEnd = 87.5_pc; 1112 const uint32_t loopCount = 8; 1113 uint32_t expectedIndex = 0u; 1114 auto expectedIterationStart = loopStart; 1115 const auto iterationIncrement = (loopEnd - loopStart) / loopCount; 1116 for (auto&& [index, loopPL] : pl.CreateLoopSubLoggersFromTo( 1117 loopStart, loopEnd, loopCount, "looping...")) { 1118 MOZ_RELEASE_ASSERT(index == expectedIndex); 1119 ++expectedIndex; 1120 MOZ_RELEASE_ASSERT( 1121 AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(), 1122 loopPL.GetGlobalProgress(), expectedIterationStart)); 1123 MOZ_RELEASE_ASSERT(AreAllEqual( 1124 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(), 1125 loopPL.GetLastGlobalLocation(), "looping...")); 1126 1127 loopPL.SetLocalProgress(50_pc, "half"); 1128 MOZ_RELEASE_ASSERT(loopPL.GetGlobalProgress() == 1129 expectedIterationStart + iterationIncrement / 2u); 1130 MOZ_RELEASE_ASSERT( 1131 AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(), 1132 loopPL.GetGlobalProgress(), 1133 expectedIterationStart + iterationIncrement / 2u)); 1134 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(), 1135 pl.GetLastGlobalLocation(), 1136 loopPL.GetLastGlobalLocation(), "half")); 1137 1138 expectedIterationStart = expectedIterationStart + iterationIncrement; 1139 } 1140 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(), 1141 pl.GetGlobalProgress(), 1142 expectedIterationStart)); 1143 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(), 1144 pl.GetLastGlobalLocation(), "looping...")); 1145 } // End of pl 1146 MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyOne()); 1147 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(), "All done")); 1148 1149 printf("TestProgressLogger done\n"); 1150 } 1151 1152 #ifdef MOZ_GECKO_PROFILER 1153 1154 [[maybe_unused]] static void SleepMilli(unsigned aMilliseconds) { 1155 # if defined(_MSC_VER) || defined(__MINGW32__) 1156 Sleep(aMilliseconds); 1157 # else 1158 struct timespec ts = {/* .tv_sec */ static_cast<time_t>(aMilliseconds / 1000), 1159 /* ts.tv_nsec */ long(aMilliseconds % 1000) * 1000000}; 1160 struct timespec tr = {0, 0}; 1161 while (nanosleep(&ts, &tr)) { 1162 if (errno == EINTR) { 1163 ts = tr; 1164 } else { 1165 printf("nanosleep() -> %s\n", strerror(errno)); 1166 exit(1); 1167 } 1168 } 1169 # endif 1170 } 1171 1172 [[maybe_unused]] static void WaitUntilTimeStampChanges( 1173 const mozilla::TimeStamp& aTimeStampToCompare = mozilla::TimeStamp::Now()) { 1174 while (aTimeStampToCompare == mozilla::TimeStamp::Now()) { 1175 SleepMilli(1); 1176 } 1177 } 1178 1179 using namespace mozilla; 1180 1181 void TestPowerOfTwoMask() { 1182 printf("TestPowerOfTwoMask...\n"); 1183 1184 static_assert(MakePowerOfTwoMask<uint32_t, 0>().MaskValue() == 0); 1185 constexpr PowerOfTwoMask<uint32_t> c0 = MakePowerOfTwoMask<uint32_t, 0>(); 1186 MOZ_RELEASE_ASSERT(c0.MaskValue() == 0); 1187 1188 static_assert(MakePowerOfTwoMask<uint32_t, 0xFFu>().MaskValue() == 0xFFu); 1189 constexpr PowerOfTwoMask<uint32_t> cFF = 1190 MakePowerOfTwoMask<uint32_t, 0xFFu>(); 1191 MOZ_RELEASE_ASSERT(cFF.MaskValue() == 0xFFu); 1192 1193 static_assert(MakePowerOfTwoMask<uint32_t, 0xFFFFFFFFu>().MaskValue() == 1194 0xFFFFFFFFu); 1195 constexpr PowerOfTwoMask<uint32_t> cFFFFFFFF = 1196 MakePowerOfTwoMask<uint32_t, 0xFFFFFFFFu>(); 1197 MOZ_RELEASE_ASSERT(cFFFFFFFF.MaskValue() == 0xFFFFFFFFu); 1198 1199 struct TestDataU32 { 1200 uint32_t mInput; 1201 uint32_t mMask; 1202 }; 1203 // clang-format off 1204 TestDataU32 tests[] = { 1205 { 0, 0 }, 1206 { 1, 1 }, 1207 { 2, 3 }, 1208 { 3, 3 }, 1209 { 4, 7 }, 1210 { 5, 7 }, 1211 { (1u << 31) - 1, (1u << 31) - 1 }, 1212 { (1u << 31), uint32_t(-1) }, 1213 { (1u << 31) + 1, uint32_t(-1) }, 1214 { uint32_t(-1), uint32_t(-1) } 1215 }; 1216 // clang-format on 1217 for (const TestDataU32& test : tests) { 1218 PowerOfTwoMask<uint32_t> p2m(test.mInput); 1219 MOZ_RELEASE_ASSERT(p2m.MaskValue() == test.mMask); 1220 for (const TestDataU32& inner : tests) { 1221 if (p2m.MaskValue() != uint32_t(-1)) { 1222 MOZ_RELEASE_ASSERT((inner.mInput % p2m) == 1223 (inner.mInput % (p2m.MaskValue() + 1))); 1224 } 1225 MOZ_RELEASE_ASSERT((inner.mInput & p2m) == (inner.mInput % p2m)); 1226 MOZ_RELEASE_ASSERT((p2m & inner.mInput) == (inner.mInput & p2m)); 1227 } 1228 } 1229 1230 printf("TestPowerOfTwoMask done\n"); 1231 } 1232 1233 void TestPowerOfTwo() { 1234 printf("TestPowerOfTwo...\n"); 1235 1236 static_assert(MakePowerOfTwo<uint32_t, 1>().Value() == 1); 1237 constexpr PowerOfTwo<uint32_t> c1 = MakePowerOfTwo<uint32_t, 1>(); 1238 MOZ_RELEASE_ASSERT(c1.Value() == 1); 1239 static_assert(MakePowerOfTwo<uint32_t, 1>().Mask().MaskValue() == 0); 1240 1241 static_assert(MakePowerOfTwo<uint32_t, 128>().Value() == 128); 1242 constexpr PowerOfTwo<uint32_t> c128 = MakePowerOfTwo<uint32_t, 128>(); 1243 MOZ_RELEASE_ASSERT(c128.Value() == 128); 1244 static_assert(MakePowerOfTwo<uint32_t, 128>().Mask().MaskValue() == 127); 1245 1246 static_assert(MakePowerOfTwo<uint32_t, 0x80000000u>().Value() == 0x80000000u); 1247 constexpr PowerOfTwo<uint32_t> cMax = MakePowerOfTwo<uint32_t, 0x80000000u>(); 1248 MOZ_RELEASE_ASSERT(cMax.Value() == 0x80000000u); 1249 static_assert(MakePowerOfTwo<uint32_t, 0x80000000u>().Mask().MaskValue() == 1250 0x7FFFFFFFu); 1251 1252 struct TestDataU32 { 1253 uint32_t mInput; 1254 uint32_t mValue; 1255 uint32_t mMask; 1256 }; 1257 // clang-format off 1258 TestDataU32 tests[] = { 1259 { 0, 1, 0 }, 1260 { 1, 1, 0 }, 1261 { 2, 2, 1 }, 1262 { 3, 4, 3 }, 1263 { 4, 4, 3 }, 1264 { 5, 8, 7 }, 1265 { (1u << 31) - 1, (1u << 31), (1u << 31) - 1 }, 1266 { (1u << 31), (1u << 31), (1u << 31) - 1 }, 1267 { (1u << 31) + 1, (1u << 31), (1u << 31) - 1 }, 1268 { uint32_t(-1), (1u << 31), (1u << 31) - 1 } 1269 }; 1270 // clang-format on 1271 for (const TestDataU32& test : tests) { 1272 PowerOfTwo<uint32_t> p2(test.mInput); 1273 MOZ_RELEASE_ASSERT(p2.Value() == test.mValue); 1274 MOZ_RELEASE_ASSERT(p2.MaskValue() == test.mMask); 1275 PowerOfTwoMask<uint32_t> p2m = p2.Mask(); 1276 MOZ_RELEASE_ASSERT(p2m.MaskValue() == test.mMask); 1277 for (const TestDataU32& inner : tests) { 1278 MOZ_RELEASE_ASSERT((inner.mInput % p2) == (inner.mInput % p2.Value())); 1279 } 1280 } 1281 1282 printf("TestPowerOfTwo done\n"); 1283 } 1284 1285 void TestLEB128() { 1286 printf("TestLEB128...\n"); 1287 1288 MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint8_t>() == 2); 1289 MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint16_t>() == 3); 1290 MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint32_t>() == 5); 1291 MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint64_t>() == 10); 1292 1293 struct TestDataU64 { 1294 uint64_t mValue; 1295 unsigned mSize; 1296 const char* mBytes; 1297 }; 1298 // clang-format off 1299 TestDataU64 tests[] = { 1300 // Small numbers should keep their normal byte representation. 1301 { 0u, 1, "\0" }, 1302 { 1u, 1, "\x01" }, 1303 1304 // 0111 1111 (127, or 0x7F) is the highest number that fits into a single 1305 // LEB128 byte. It gets encoded as 0111 1111, note the most significant bit 1306 // is off. 1307 { 0x7Fu, 1, "\x7F" }, 1308 1309 // Next number: 128, or 0x80. 1310 // Original data representation: 1000 0000 1311 // Broken up into groups of 7: 1 0000000 1312 // Padded with 0 (msB) or 1 (lsB): 00000001 10000000 1313 // Byte representation: 0x01 0x80 1314 // Little endian order: -> 0x80 0x01 1315 { 0x80u, 2, "\x80\x01" }, 1316 1317 // Next: 129, or 0x81 (showing that we don't lose low bits.) 1318 // Original data representation: 1000 0001 1319 // Broken up into groups of 7: 1 0000001 1320 // Padded with 0 (msB) or 1 (lsB): 00000001 10000001 1321 // Byte representation: 0x01 0x81 1322 // Little endian order: -> 0x81 0x01 1323 { 0x81u, 2, "\x81\x01" }, 1324 1325 // Highest 8-bit number: 255, or 0xFF. 1326 // Original data representation: 1111 1111 1327 // Broken up into groups of 7: 1 1111111 1328 // Padded with 0 (msB) or 1 (lsB): 00000001 11111111 1329 // Byte representation: 0x01 0xFF 1330 // Little endian order: -> 0xFF 0x01 1331 { 0xFFu, 2, "\xFF\x01" }, 1332 1333 // Next: 256, or 0x100. 1334 // Original data representation: 1 0000 0000 1335 // Broken up into groups of 7: 10 0000000 1336 // Padded with 0 (msB) or 1 (lsB): 00000010 10000000 1337 // Byte representation: 0x10 0x80 1338 // Little endian order: -> 0x80 0x02 1339 { 0x100u, 2, "\x80\x02" }, 1340 1341 // Highest 32-bit number: 0xFFFFFFFF (8 bytes, all bits set). 1342 // Original: 1111 1111 1111 1111 1111 1111 1111 1111 1343 // Groups: 1111 1111111 1111111 1111111 1111111 1344 // Padded: 00001111 11111111 11111111 11111111 11111111 1345 // Bytes: 0x0F 0xFF 0xFF 0xFF 0xFF 1346 // Little Endian: -> 0xFF 0xFF 0xFF 0xFF 0x0F 1347 { 0xFFFFFFFFu, 5, "\xFF\xFF\xFF\xFF\x0F" }, 1348 1349 // Highest 64-bit number: 0xFFFFFFFFFFFFFFFF (16 bytes, all bits set). 1350 // 64 bits, that's 9 groups of 7 bits, plus 1 (most significant) bit. 1351 { 0xFFFFFFFFFFFFFFFFu, 10, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01" } 1352 }; 1353 // clang-format on 1354 1355 for (const TestDataU64& test : tests) { 1356 MOZ_RELEASE_ASSERT(ULEB128Size(test.mValue) == test.mSize); 1357 // Prepare a buffer that can accomodate the largest-possible LEB128. 1358 uint8_t buffer[ULEB128MaxSize<uint64_t>()]; 1359 // Use a pointer into the buffer as iterator. 1360 uint8_t* p = buffer; 1361 // And write the LEB128. 1362 WriteULEB128(test.mValue, p); 1363 // Pointer (iterator) should have advanced just past the expected LEB128 1364 // size. 1365 MOZ_RELEASE_ASSERT(p == buffer + test.mSize); 1366 // Check expected bytes. 1367 for (unsigned i = 0; i < test.mSize; ++i) { 1368 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t(test.mBytes[i])); 1369 } 1370 1371 // Move pointer (iterator) back to start of buffer. 1372 p = buffer; 1373 // And read the LEB128 we wrote above. 1374 uint64_t read = ReadULEB128<uint64_t>(p); 1375 // Pointer (iterator) should have also advanced just past the expected 1376 // LEB128 size. 1377 MOZ_RELEASE_ASSERT(p == buffer + test.mSize); 1378 // And check the read value. 1379 MOZ_RELEASE_ASSERT(read == test.mValue); 1380 1381 // Testing ULEB128 reader. 1382 ULEB128Reader<uint64_t> reader; 1383 MOZ_RELEASE_ASSERT(!reader.IsComplete()); 1384 // Move pointer back to start of buffer. 1385 p = buffer; 1386 for (;;) { 1387 // Read a byte and feed it to the reader. 1388 if (reader.FeedByteIsComplete(*p++)) { 1389 break; 1390 } 1391 // Not complete yet, we shouldn't have reached the end pointer. 1392 MOZ_RELEASE_ASSERT(!reader.IsComplete()); 1393 MOZ_RELEASE_ASSERT(p < buffer + test.mSize); 1394 } 1395 MOZ_RELEASE_ASSERT(reader.IsComplete()); 1396 // Pointer should have advanced just past the expected LEB128 size. 1397 MOZ_RELEASE_ASSERT(p == buffer + test.mSize); 1398 // And check the read value. 1399 MOZ_RELEASE_ASSERT(reader.Value() == test.mValue); 1400 1401 // And again after a Reset. 1402 reader.Reset(); 1403 MOZ_RELEASE_ASSERT(!reader.IsComplete()); 1404 p = buffer; 1405 for (;;) { 1406 if (reader.FeedByteIsComplete(*p++)) { 1407 break; 1408 } 1409 MOZ_RELEASE_ASSERT(!reader.IsComplete()); 1410 MOZ_RELEASE_ASSERT(p < buffer + test.mSize); 1411 } 1412 MOZ_RELEASE_ASSERT(reader.IsComplete()); 1413 MOZ_RELEASE_ASSERT(p == buffer + test.mSize); 1414 MOZ_RELEASE_ASSERT(reader.Value() == test.mValue); 1415 } 1416 1417 printf("TestLEB128 done\n"); 1418 } 1419 1420 struct StringWriteFunc final : public JSONWriteFunc { 1421 std::string mString; 1422 1423 void Write(const mozilla::Span<const char>& aStr) final { 1424 mString.append(aStr.data(), aStr.size()); 1425 } 1426 }; 1427 1428 void CheckJSON(mozilla::baseprofiler::SpliceableJSONWriter& aWriter, 1429 const char* aExpected, int aLine) { 1430 const std::string& actual = 1431 static_cast<StringWriteFunc&>(aWriter.WriteFunc()).mString; 1432 if (strcmp(aExpected, actual.c_str()) != 0) { 1433 fprintf(stderr, 1434 "---- EXPECTED ---- (line %d)\n<<<%s>>>\n" 1435 "---- ACTUAL ----\n<<<%s>>>\n", 1436 aLine, aExpected, actual.c_str()); 1437 MOZ_RELEASE_ASSERT(false, "expected and actual output don't match"); 1438 } 1439 } 1440 1441 void TestJSONTimeOutput() { 1442 printf("TestJSONTimeOutput...\n"); 1443 1444 # define TEST(in, out) \ 1445 do { \ 1446 mozilla::baseprofiler::SpliceableJSONWriter writer( \ 1447 mozilla::MakeUnique<StringWriteFunc>(), \ 1448 FailureLatchInfallibleSource::Singleton()); \ 1449 writer.Start(); \ 1450 writer.TimeDoubleMsProperty("time_ms", (in)); \ 1451 writer.End(); \ 1452 CheckJSON(writer, "{\"time_ms\":" out "}", __LINE__); \ 1453 } while (false); 1454 1455 TEST(0, "0"); 1456 1457 TEST(0.000'000'1, "0"); 1458 TEST(0.000'000'4, "0"); 1459 TEST(0.000'000'499, "0"); 1460 TEST(0.000'000'5, "0.000001"); 1461 TEST(0.000'001, "0.000001"); 1462 TEST(0.000'01, "0.00001"); 1463 TEST(0.000'1, "0.0001"); 1464 TEST(0.001, "0.001"); 1465 TEST(0.01, "0.01"); 1466 TEST(0.1, "0.1"); 1467 TEST(1, "1"); 1468 TEST(2, "2"); 1469 TEST(10, "10"); 1470 TEST(100, "100"); 1471 TEST(1'000, "1000"); 1472 TEST(10'000, "10000"); 1473 TEST(100'000, "100000"); 1474 TEST(1'000'000, "1000000"); 1475 // 2^53-2 ns in ms. 2^53-1 is the highest integer value representable in 1476 // double, -1 again because we're adding 0.5 before truncating. 1477 // That's 104 days, after which the nanosecond precision would decrease. 1478 TEST(9'007'199'254.740'990, "9007199254.74099"); 1479 1480 TEST(-0.000'000'1, "0"); 1481 TEST(-0.000'000'4, "0"); 1482 TEST(-0.000'000'499, "0"); 1483 TEST(-0.000'000'5, "-0.000001"); 1484 TEST(-0.000'001, "-0.000001"); 1485 TEST(-0.000'01, "-0.00001"); 1486 TEST(-0.000'1, "-0.0001"); 1487 TEST(-0.001, "-0.001"); 1488 TEST(-0.01, "-0.01"); 1489 TEST(-0.1, "-0.1"); 1490 TEST(-1, "-1"); 1491 TEST(-2, "-2"); 1492 TEST(-10, "-10"); 1493 TEST(-100, "-100"); 1494 TEST(-1'000, "-1000"); 1495 TEST(-10'000, "-10000"); 1496 TEST(-100'000, "-100000"); 1497 TEST(-1'000'000, "-1000000"); 1498 TEST(-9'007'199'254.740'990, "-9007199254.74099"); 1499 1500 # undef TEST 1501 1502 printf("TestJSONTimeOutput done\n"); 1503 } 1504 1505 template <uint8_t byte, uint8_t... tail> 1506 constexpr bool TestConstexprULEB128Reader(ULEB128Reader<uint64_t>& aReader) { 1507 if (aReader.IsComplete()) { 1508 return false; 1509 } 1510 const bool isComplete = aReader.FeedByteIsComplete(byte); 1511 if (aReader.IsComplete() != isComplete) { 1512 return false; 1513 } 1514 if constexpr (sizeof...(tail) == 0) { 1515 return isComplete; 1516 } else { 1517 if (isComplete) { 1518 return false; 1519 } 1520 return TestConstexprULEB128Reader<tail...>(aReader); 1521 } 1522 } 1523 1524 template <uint64_t expected, uint8_t... bytes> 1525 constexpr bool TestConstexprULEB128Reader() { 1526 ULEB128Reader<uint64_t> reader; 1527 if (!TestConstexprULEB128Reader<bytes...>(reader)) { 1528 return false; 1529 } 1530 if (!reader.IsComplete()) { 1531 return false; 1532 } 1533 if (reader.Value() != expected) { 1534 return false; 1535 } 1536 1537 reader.Reset(); 1538 if (!TestConstexprULEB128Reader<bytes...>(reader)) { 1539 return false; 1540 } 1541 if (!reader.IsComplete()) { 1542 return false; 1543 } 1544 if (reader.Value() != expected) { 1545 return false; 1546 } 1547 1548 return true; 1549 } 1550 1551 static_assert(TestConstexprULEB128Reader<0x0u, 0x0u>()); 1552 static_assert(!TestConstexprULEB128Reader<0x0u, 0x0u, 0x0u>()); 1553 static_assert(TestConstexprULEB128Reader<0x1u, 0x1u>()); 1554 static_assert(TestConstexprULEB128Reader<0x7Fu, 0x7Fu>()); 1555 static_assert(TestConstexprULEB128Reader<0x80u, 0x80u, 0x01u>()); 1556 static_assert(!TestConstexprULEB128Reader<0x80u, 0x80u>()); 1557 static_assert(!TestConstexprULEB128Reader<0x80u, 0x01u>()); 1558 static_assert(TestConstexprULEB128Reader<0x81u, 0x81u, 0x01u>()); 1559 static_assert(TestConstexprULEB128Reader<0xFFu, 0xFFu, 0x01u>()); 1560 static_assert(TestConstexprULEB128Reader<0x100u, 0x80u, 0x02u>()); 1561 static_assert(TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 1562 0xFFu, 0x0Fu>()); 1563 static_assert( 1564 !TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu>()); 1565 static_assert(!TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 1566 0xFFu, 0xFFu, 0x0Fu>()); 1567 static_assert( 1568 TestConstexprULEB128Reader<0xFFFFFFFFFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 1569 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0x01u>()); 1570 static_assert( 1571 !TestConstexprULEB128Reader<0xFFFFFFFFFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 1572 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu>()); 1573 1574 static void TestChunk() { 1575 printf("TestChunk...\n"); 1576 1577 static_assert(!std::is_default_constructible_v<ProfileBufferChunk>, 1578 "ProfileBufferChunk should not be default-constructible"); 1579 static_assert( 1580 !std::is_constructible_v<ProfileBufferChunk, ProfileBufferChunk::Length>, 1581 "ProfileBufferChunk should not be constructible from Length"); 1582 1583 static_assert( 1584 sizeof(ProfileBufferChunk::Header) == 1585 sizeof(ProfileBufferChunk::Header::mOffsetFirstBlock) + 1586 sizeof(ProfileBufferChunk::Header::mOffsetPastLastBlock) + 1587 sizeof(ProfileBufferChunk::Header::mStartTimeStamp) + 1588 sizeof(ProfileBufferChunk::Header::mDoneTimeStamp) + 1589 sizeof(ProfileBufferChunk::Header::mBufferBytes) + 1590 sizeof(ProfileBufferChunk::Header::mBlockCount) + 1591 sizeof(ProfileBufferChunk::Header::mRangeStart) + 1592 sizeof(ProfileBufferChunk::Header::mProcessId) + 1593 sizeof(ProfileBufferChunk::Header::mPADDING), 1594 "ProfileBufferChunk::Header may have unwanted padding, please review"); 1595 // Note: The above static_assert is an attempt at keeping 1596 // ProfileBufferChunk::Header tightly packed, but some changes could make this 1597 // impossible to achieve (most probably due to alignment) -- Just do your 1598 // best! 1599 1600 constexpr ProfileBufferChunk::Length TestLen = 1000; 1601 1602 // Basic allocations of different sizes. 1603 for (ProfileBufferChunk::Length len = 0; len <= TestLen; ++len) { 1604 auto chunk = ProfileBufferChunk::Create(len); 1605 static_assert( 1606 std::is_same_v<decltype(chunk), UniquePtr<ProfileBufferChunk>>, 1607 "ProfileBufferChunk::Create() should return a " 1608 "UniquePtr<ProfileBufferChunk>"); 1609 MOZ_RELEASE_ASSERT(!!chunk, "OOM!?"); 1610 MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= len); 1611 MOZ_RELEASE_ASSERT(chunk->ChunkBytes() >= 1612 len + ProfileBufferChunk::SizeofChunkMetadata()); 1613 MOZ_RELEASE_ASSERT(chunk->RemainingBytes() == chunk->BufferBytes()); 1614 MOZ_RELEASE_ASSERT(chunk->OffsetFirstBlock() == 0); 1615 MOZ_RELEASE_ASSERT(chunk->OffsetPastLastBlock() == 0); 1616 MOZ_RELEASE_ASSERT(chunk->BlockCount() == 0); 1617 MOZ_RELEASE_ASSERT(chunk->ProcessId() == 0); 1618 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0); 1619 MOZ_RELEASE_ASSERT(chunk->BufferSpan().LengthBytes() == 1620 chunk->BufferBytes()); 1621 MOZ_RELEASE_ASSERT(!chunk->GetNext()); 1622 MOZ_RELEASE_ASSERT(!chunk->ReleaseNext()); 1623 MOZ_RELEASE_ASSERT(chunk->Last() == chunk.get()); 1624 } 1625 1626 // Allocate the main test Chunk. 1627 auto chunkA = ProfileBufferChunk::Create(TestLen); 1628 MOZ_RELEASE_ASSERT(!!chunkA, "OOM!?"); 1629 MOZ_RELEASE_ASSERT(chunkA->BufferBytes() >= TestLen); 1630 MOZ_RELEASE_ASSERT(chunkA->ChunkBytes() >= 1631 TestLen + ProfileBufferChunk::SizeofChunkMetadata()); 1632 MOZ_RELEASE_ASSERT(!chunkA->GetNext()); 1633 MOZ_RELEASE_ASSERT(!chunkA->ReleaseNext()); 1634 1635 constexpr ProfileBufferIndex chunkARangeStart = 12345; 1636 chunkA->SetRangeStart(chunkARangeStart); 1637 MOZ_RELEASE_ASSERT(chunkA->RangeStart() == chunkARangeStart); 1638 1639 // Get a read-only span over its buffer. 1640 auto bufferA = chunkA->BufferSpan(); 1641 static_assert( 1642 std::is_same_v<decltype(bufferA), Span<const ProfileBufferChunk::Byte>>, 1643 "BufferSpan() should return a Span<const Byte>"); 1644 MOZ_RELEASE_ASSERT(bufferA.LengthBytes() == chunkA->BufferBytes()); 1645 1646 // Add the initial tail block. 1647 constexpr ProfileBufferChunk::Length initTailLen = 10; 1648 auto initTail = chunkA->ReserveInitialBlockAsTail(initTailLen); 1649 static_assert( 1650 std::is_same_v<decltype(initTail), Span<ProfileBufferChunk::Byte>>, 1651 "ReserveInitialBlockAsTail() should return a Span<Byte>"); 1652 MOZ_RELEASE_ASSERT(initTail.LengthBytes() == initTailLen); 1653 MOZ_RELEASE_ASSERT(initTail.Elements() == bufferA.Elements()); 1654 MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == initTailLen); 1655 MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == initTailLen); 1656 1657 // Add the first complete block. 1658 constexpr ProfileBufferChunk::Length block1Len = 20; 1659 auto block1 = chunkA->ReserveBlock(block1Len); 1660 static_assert( 1661 std::is_same_v<decltype(block1), ProfileBufferChunk::ReserveReturn>, 1662 "ReserveBlock() should return a ReserveReturn"); 1663 MOZ_RELEASE_ASSERT(block1.mBlockRangeIndex.ConvertToProfileBufferIndex() == 1664 chunkARangeStart + initTailLen); 1665 MOZ_RELEASE_ASSERT(block1.mSpan.LengthBytes() == block1Len); 1666 MOZ_RELEASE_ASSERT(block1.mSpan.Elements() == 1667 bufferA.Elements() + initTailLen); 1668 MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == initTailLen); 1669 MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == initTailLen + block1Len); 1670 MOZ_RELEASE_ASSERT(chunkA->RemainingBytes() != 0); 1671 1672 // Add another block to over-fill the ProfileBufferChunk. 1673 const ProfileBufferChunk::Length remaining = 1674 chunkA->BufferBytes() - (initTailLen + block1Len); 1675 constexpr ProfileBufferChunk::Length overfill = 30; 1676 const ProfileBufferChunk::Length block2Len = remaining + overfill; 1677 ProfileBufferChunk::ReserveReturn block2 = chunkA->ReserveBlock(block2Len); 1678 MOZ_RELEASE_ASSERT(block2.mBlockRangeIndex.ConvertToProfileBufferIndex() == 1679 chunkARangeStart + initTailLen + block1Len); 1680 MOZ_RELEASE_ASSERT(block2.mSpan.LengthBytes() == remaining); 1681 MOZ_RELEASE_ASSERT(block2.mSpan.Elements() == 1682 bufferA.Elements() + initTailLen + block1Len); 1683 MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == initTailLen); 1684 MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == chunkA->BufferBytes()); 1685 MOZ_RELEASE_ASSERT(chunkA->RemainingBytes() == 0); 1686 1687 // Block must be marked "done" before it can be recycled. 1688 chunkA->MarkDone(); 1689 1690 // It must be marked "recycled" before data can be added to it again. 1691 chunkA->MarkRecycled(); 1692 1693 // Add an empty initial tail block. 1694 Span<ProfileBufferChunk::Byte> initTail2 = 1695 chunkA->ReserveInitialBlockAsTail(0); 1696 MOZ_RELEASE_ASSERT(initTail2.LengthBytes() == 0); 1697 MOZ_RELEASE_ASSERT(initTail2.Elements() == bufferA.Elements()); 1698 MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == 0); 1699 MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == 0); 1700 1701 // Block must be marked "done" before it can be destroyed. 1702 chunkA->MarkDone(); 1703 1704 chunkA->SetProcessId(123); 1705 MOZ_RELEASE_ASSERT(chunkA->ProcessId() == 123); 1706 1707 printf("TestChunk done\n"); 1708 } 1709 1710 static void TestChunkManagerSingle() { 1711 printf("TestChunkManagerSingle...\n"); 1712 1713 // Construct a ProfileBufferChunkManagerSingle for one chunk of size >=1000. 1714 constexpr ProfileBufferChunk::Length ChunkMinBufferBytes = 1000; 1715 ProfileBufferChunkManagerSingle cms{ChunkMinBufferBytes}; 1716 1717 // Reference to base class, to exercize virtual methods. 1718 ProfileBufferChunkManager& cm = cms; 1719 1720 # ifdef DEBUG 1721 const char* chunkManagerRegisterer = "TestChunkManagerSingle"; 1722 cm.RegisteredWith(chunkManagerRegisterer); 1723 # endif // DEBUG 1724 1725 const auto maxTotalSize = cm.MaxTotalSize(); 1726 MOZ_RELEASE_ASSERT(maxTotalSize >= ChunkMinBufferBytes); 1727 1728 cm.SetChunkDestroyedCallback([](const ProfileBufferChunk&) { 1729 MOZ_RELEASE_ASSERT( 1730 false, 1731 "ProfileBufferChunkManagerSingle should never destroy its one chunk"); 1732 }); 1733 1734 UniquePtr<ProfileBufferChunk> extantReleasedChunks = 1735 cm.GetExtantReleasedChunks(); 1736 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)"); 1737 1738 // First request. 1739 UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk(); 1740 MOZ_RELEASE_ASSERT(!!chunk, "First chunk request should always work"); 1741 MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= ChunkMinBufferBytes, 1742 "Unexpected chunk size"); 1743 MOZ_RELEASE_ASSERT(!chunk->GetNext(), "There should only be one chunk"); 1744 1745 // Keep address, for later checks. 1746 const uintptr_t chunkAddress = reinterpret_cast<uintptr_t>(chunk.get()); 1747 1748 extantReleasedChunks = cm.GetExtantReleasedChunks(); 1749 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)"); 1750 1751 // Second request. 1752 MOZ_RELEASE_ASSERT(!cm.GetChunk(), "Second chunk request should always fail"); 1753 1754 extantReleasedChunks = cm.GetExtantReleasedChunks(); 1755 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)"); 1756 1757 // Add some data to the chunk (to verify recycling later on). 1758 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 0); 1759 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 0); 1760 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0); 1761 chunk->SetRangeStart(100); 1762 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 100); 1763 (void)chunk->ReserveInitialBlockAsTail(1); 1764 (void)chunk->ReserveBlock(2); 1765 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 1); 1766 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 1 + 2); 1767 1768 // Release the first chunk. 1769 chunk->MarkDone(); 1770 cm.ReleaseChunk(std::move(chunk)); 1771 MOZ_RELEASE_ASSERT(!chunk, "chunk UniquePtr should have been moved-from"); 1772 1773 // Request after release. 1774 MOZ_RELEASE_ASSERT(!cm.GetChunk(), 1775 "Chunk request after release should also fail"); 1776 1777 // Check released chunk. 1778 extantReleasedChunks = cm.GetExtantReleasedChunks(); 1779 MOZ_RELEASE_ASSERT(!!extantReleasedChunks, 1780 "Could not retrieve released chunk"); 1781 MOZ_RELEASE_ASSERT(!extantReleasedChunks->GetNext(), 1782 "There should only be one released chunk"); 1783 MOZ_RELEASE_ASSERT( 1784 reinterpret_cast<uintptr_t>(extantReleasedChunks.get()) == chunkAddress, 1785 "Released chunk should be first requested one"); 1786 1787 MOZ_RELEASE_ASSERT(!cm.GetExtantReleasedChunks(), 1788 "Unexpected extra released chunk(s)"); 1789 1790 // Another request after release. 1791 MOZ_RELEASE_ASSERT(!cm.GetChunk(), 1792 "Chunk request after release should also fail"); 1793 1794 MOZ_RELEASE_ASSERT( 1795 cm.MaxTotalSize() == maxTotalSize, 1796 "MaxTotalSize() should not change after requests&releases"); 1797 1798 // Reset the chunk manager. (Single-only non-virtual function.) 1799 cms.Reset(std::move(extantReleasedChunks)); 1800 MOZ_RELEASE_ASSERT(!extantReleasedChunks, 1801 "Released chunk UniquePtr should have been moved-from"); 1802 1803 MOZ_RELEASE_ASSERT( 1804 cm.MaxTotalSize() == maxTotalSize, 1805 "MaxTotalSize() should not change when resetting with the same chunk"); 1806 1807 // 2nd round, first request. Theoretically async, but this implementation just 1808 // immediately runs the callback. 1809 bool ran = false; 1810 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) { 1811 ran = true; 1812 MOZ_RELEASE_ASSERT(!!aChunk); 1813 chunk = std::move(aChunk); 1814 }); 1815 MOZ_RELEASE_ASSERT(ran, "RequestChunk callback not called immediately"); 1816 ran = false; 1817 cm.FulfillChunkRequests(); 1818 MOZ_RELEASE_ASSERT(!ran, "FulfillChunkRequests should not have any effects"); 1819 MOZ_RELEASE_ASSERT(!!chunk, "First chunk request should always work"); 1820 MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= ChunkMinBufferBytes, 1821 "Unexpected chunk size"); 1822 MOZ_RELEASE_ASSERT(!chunk->GetNext(), "There should only be one chunk"); 1823 MOZ_RELEASE_ASSERT(reinterpret_cast<uintptr_t>(chunk.get()) == chunkAddress, 1824 "Requested chunk should be first requested one"); 1825 // Verify that chunk is empty and usable. 1826 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 0); 1827 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 0); 1828 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0); 1829 chunk->SetRangeStart(200); 1830 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 200); 1831 (void)chunk->ReserveInitialBlockAsTail(3); 1832 (void)chunk->ReserveBlock(4); 1833 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 3); 1834 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 3 + 4); 1835 1836 // Second request. 1837 ran = false; 1838 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) { 1839 ran = true; 1840 MOZ_RELEASE_ASSERT(!aChunk, "Second chunk request should always fail"); 1841 }); 1842 MOZ_RELEASE_ASSERT(ran, "RequestChunk callback not called"); 1843 1844 // This one does nothing. 1845 cm.ForgetUnreleasedChunks(); 1846 1847 // Don't forget to mark chunk "Done" before letting it die. 1848 chunk->MarkDone(); 1849 chunk = nullptr; 1850 1851 // Create a tiny chunk and reset the chunk manager with it. 1852 chunk = ProfileBufferChunk::Create(1); 1853 MOZ_RELEASE_ASSERT(!!chunk); 1854 auto tinyChunkSize = chunk->BufferBytes(); 1855 MOZ_RELEASE_ASSERT(tinyChunkSize >= 1); 1856 MOZ_RELEASE_ASSERT(tinyChunkSize < ChunkMinBufferBytes); 1857 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0); 1858 chunk->SetRangeStart(300); 1859 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 300); 1860 cms.Reset(std::move(chunk)); 1861 MOZ_RELEASE_ASSERT(!chunk, "chunk UniquePtr should have been moved-from"); 1862 MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == tinyChunkSize, 1863 "MaxTotalSize() should match the new chunk size"); 1864 chunk = cm.GetChunk(); 1865 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0, "Got non-recycled chunk"); 1866 1867 // Enough testing! Clean-up. 1868 (void)chunk->ReserveInitialBlockAsTail(0); 1869 chunk->MarkDone(); 1870 cm.ForgetUnreleasedChunks(); 1871 1872 # ifdef DEBUG 1873 cm.DeregisteredFrom(chunkManagerRegisterer); 1874 # endif // DEBUG 1875 1876 printf("TestChunkManagerSingle done\n"); 1877 } 1878 1879 static void TestChunkManagerWithLocalLimit() { 1880 printf("TestChunkManagerWithLocalLimit...\n"); 1881 1882 // Construct a ProfileBufferChunkManagerWithLocalLimit with chunk of minimum 1883 // size >=100, up to 1000 bytes. 1884 constexpr ProfileBufferChunk::Length MaxTotalBytes = 1000; 1885 constexpr ProfileBufferChunk::Length ChunkMinBufferBytes = 100; 1886 ProfileBufferChunkManagerWithLocalLimit cmll{MaxTotalBytes, 1887 ChunkMinBufferBytes}; 1888 1889 // Reference to base class, to exercize virtual methods. 1890 ProfileBufferChunkManager& cm = cmll; 1891 1892 # ifdef DEBUG 1893 const char* chunkManagerRegisterer = "TestChunkManagerWithLocalLimit"; 1894 cm.RegisteredWith(chunkManagerRegisterer); 1895 # endif // DEBUG 1896 1897 MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == MaxTotalBytes, 1898 "Max total size should be exactly as given"); 1899 1900 unsigned destroyedChunks = 0; 1901 unsigned destroyedBytes = 0; 1902 cm.SetChunkDestroyedCallback([&](const ProfileBufferChunk& aChunks) { 1903 for (const ProfileBufferChunk* chunk = &aChunks; chunk; 1904 chunk = chunk->GetNext()) { 1905 destroyedChunks += 1; 1906 destroyedBytes += chunk->BufferBytes(); 1907 } 1908 }); 1909 1910 UniquePtr<ProfileBufferChunk> extantReleasedChunks = 1911 cm.GetExtantReleasedChunks(); 1912 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)"); 1913 1914 // First request. 1915 UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk(); 1916 MOZ_RELEASE_ASSERT(!!chunk, 1917 "First chunk immediate request should always work"); 1918 const auto chunkActualBufferBytes = chunk->BufferBytes(); 1919 MOZ_RELEASE_ASSERT(chunkActualBufferBytes >= ChunkMinBufferBytes, 1920 "Unexpected chunk size"); 1921 MOZ_RELEASE_ASSERT(!chunk->GetNext(), "There should only be one chunk"); 1922 1923 // Keep address, for later checks. 1924 const uintptr_t chunk1Address = reinterpret_cast<uintptr_t>(chunk.get()); 1925 1926 extantReleasedChunks = cm.GetExtantReleasedChunks(); 1927 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)"); 1928 1929 // Verify that ReleaseChunk accepts zero chunks. 1930 cm.ReleaseChunk(nullptr); 1931 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)"); 1932 1933 // For this test, we need to be able to get at least 2 chunks without hitting 1934 // the limit. (If this failed, it wouldn't necessary be a problem with 1935 // ProfileBufferChunkManagerWithLocalLimit, fiddle with constants at the top 1936 // of this test.) 1937 MOZ_RELEASE_ASSERT(chunkActualBufferBytes < 2 * MaxTotalBytes); 1938 1939 unsigned chunk1ReuseCount = 0; 1940 1941 // We will do enough loops to go through the maximum size a number of times. 1942 const unsigned Rollovers = 3; 1943 const unsigned Loops = Rollovers * MaxTotalBytes / chunkActualBufferBytes; 1944 for (unsigned i = 0; i < Loops; ++i) { 1945 // Add some data to the chunk. 1946 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 0); 1947 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 0); 1948 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0); 1949 const ProfileBufferIndex index = 1 + i * chunkActualBufferBytes; 1950 chunk->SetRangeStart(index); 1951 MOZ_RELEASE_ASSERT(chunk->RangeStart() == index); 1952 (void)chunk->ReserveInitialBlockAsTail(1); 1953 (void)chunk->ReserveBlock(2); 1954 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 1); 1955 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 1 + 2); 1956 1957 // Request a new chunk. 1958 bool ran = false; 1959 UniquePtr<ProfileBufferChunk> newChunk; 1960 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) { 1961 ran = true; 1962 newChunk = std::move(aChunk); 1963 }); 1964 MOZ_RELEASE_ASSERT( 1965 !ran, "RequestChunk should not immediately fulfill the request"); 1966 cm.FulfillChunkRequests(); 1967 MOZ_RELEASE_ASSERT(ran, "FulfillChunkRequests should invoke the callback"); 1968 MOZ_RELEASE_ASSERT(!!newChunk, "Chunk request should always work"); 1969 MOZ_RELEASE_ASSERT(newChunk->BufferBytes() == chunkActualBufferBytes, 1970 "Unexpected chunk size"); 1971 MOZ_RELEASE_ASSERT(!newChunk->GetNext(), "There should only be one chunk"); 1972 1973 // Mark previous chunk done and release it. 1974 WaitUntilTimeStampChanges(); // Force "done" timestamp to change. 1975 chunk->MarkDone(); 1976 cm.ReleaseChunk(std::move(chunk)); 1977 1978 // And cycle to the new chunk. 1979 chunk = std::move(newChunk); 1980 1981 if (reinterpret_cast<uintptr_t>(chunk.get()) == chunk1Address) { 1982 ++chunk1ReuseCount; 1983 } 1984 } 1985 1986 // Expect all rollovers except 1 to destroy chunks. 1987 MOZ_RELEASE_ASSERT(destroyedChunks >= (Rollovers - 1) * MaxTotalBytes / 1988 chunkActualBufferBytes, 1989 "Not enough destroyed chunks"); 1990 MOZ_RELEASE_ASSERT(destroyedBytes == destroyedChunks * chunkActualBufferBytes, 1991 "Mismatched destroyed chunks and bytes"); 1992 MOZ_RELEASE_ASSERT(chunk1ReuseCount >= (Rollovers - 1), 1993 "Not enough reuse of the first chunks"); 1994 1995 // Check that chunk manager is reentrant from request callback. 1996 bool ran = false; 1997 bool ranInner = false; 1998 UniquePtr<ProfileBufferChunk> newChunk; 1999 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) { 2000 ran = true; 2001 MOZ_RELEASE_ASSERT(!!aChunk, "Chunk request should always work"); 2002 (void)aChunk->ReserveInitialBlockAsTail(0); 2003 WaitUntilTimeStampChanges(); // Force "done" timestamp to change. 2004 aChunk->MarkDone(); 2005 UniquePtr<ProfileBufferChunk> anotherChunk = cm.GetChunk(); 2006 MOZ_RELEASE_ASSERT(!!anotherChunk); 2007 (void)anotherChunk->ReserveInitialBlockAsTail(0); 2008 WaitUntilTimeStampChanges(); // Force "done" timestamp to change. 2009 anotherChunk->MarkDone(); 2010 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) { 2011 ranInner = true; 2012 MOZ_RELEASE_ASSERT(!!aChunk, "Chunk request should always work"); 2013 (void)aChunk->ReserveInitialBlockAsTail(0); 2014 WaitUntilTimeStampChanges(); // Force "done" timestamp to change. 2015 aChunk->MarkDone(); 2016 }); 2017 MOZ_RELEASE_ASSERT( 2018 !ranInner, "RequestChunk should not immediately fulfill the request"); 2019 }); 2020 MOZ_RELEASE_ASSERT(!ran, 2021 "RequestChunk should not immediately fulfill the request"); 2022 MOZ_RELEASE_ASSERT( 2023 !ranInner, 2024 "RequestChunk should not immediately fulfill the inner request"); 2025 cm.FulfillChunkRequests(); 2026 MOZ_RELEASE_ASSERT(ran, "FulfillChunkRequests should invoke the callback"); 2027 MOZ_RELEASE_ASSERT(!ranInner, 2028 "FulfillChunkRequests should not immediately fulfill " 2029 "the inner request"); 2030 cm.FulfillChunkRequests(); 2031 MOZ_RELEASE_ASSERT( 2032 ran, "2nd FulfillChunkRequests should invoke the inner request callback"); 2033 2034 // Enough testing! Clean-up. 2035 (void)chunk->ReserveInitialBlockAsTail(0); 2036 WaitUntilTimeStampChanges(); // Force "done" timestamp to change. 2037 chunk->MarkDone(); 2038 cm.ForgetUnreleasedChunks(); 2039 2040 // Special testing of the release algorithm, to make sure released chunks get 2041 // sorted. 2042 constexpr unsigned RandomReleaseChunkLoop = 100; 2043 // Build a vector of chunks, and mark them "done", ready to be released. 2044 Vector<UniquePtr<ProfileBufferChunk>> chunksToRelease; 2045 MOZ_RELEASE_ASSERT(chunksToRelease.reserve(RandomReleaseChunkLoop)); 2046 Vector<TimeStamp> chunksTimeStamps; 2047 MOZ_RELEASE_ASSERT(chunksTimeStamps.reserve(RandomReleaseChunkLoop)); 2048 for (unsigned i = 0; i < RandomReleaseChunkLoop; ++i) { 2049 UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk(); 2050 MOZ_RELEASE_ASSERT(chunk); 2051 (void)chunk->ReserveInitialBlockAsTail(0); 2052 chunk->MarkDone(); 2053 MOZ_RELEASE_ASSERT(!chunk->ChunkHeader().mDoneTimeStamp.IsNull()); 2054 chunksTimeStamps.infallibleEmplaceBack(chunk->ChunkHeader().mDoneTimeStamp); 2055 chunksToRelease.infallibleEmplaceBack(std::move(chunk)); 2056 if (i % 10 == 0) { 2057 // "Done" timestamps should *usually* increase, let's make extra sure some 2058 // timestamps are actually different. 2059 WaitUntilTimeStampChanges(); 2060 } 2061 } 2062 // Shuffle the list. 2063 std::random_device randomDevice; 2064 std::mt19937 generator(randomDevice()); 2065 std::shuffle(chunksToRelease.begin(), chunksToRelease.end(), generator); 2066 // And release chunks one by one, checking that the list of released chunks 2067 // is always sorted. 2068 printf("TestChunkManagerWithLocalLimit - Shuffle test timestamps:"); 2069 for (unsigned i = 0; i < RandomReleaseChunkLoop; ++i) { 2070 printf(" %f", (chunksToRelease[i]->ChunkHeader().mDoneTimeStamp - 2071 TimeStamp::ProcessCreation()) 2072 .ToMicroseconds()); 2073 cm.ReleaseChunk(std::move(chunksToRelease[i])); 2074 cm.PeekExtantReleasedChunks([i](const ProfileBufferChunk* releasedChunks) { 2075 MOZ_RELEASE_ASSERT(releasedChunks); 2076 unsigned releasedChunkCount = 1; 2077 for (;;) { 2078 const ProfileBufferChunk* nextChunk = releasedChunks->GetNext(); 2079 if (!nextChunk) { 2080 break; 2081 } 2082 ++releasedChunkCount; 2083 MOZ_RELEASE_ASSERT(releasedChunks->ChunkHeader().mDoneTimeStamp <= 2084 nextChunk->ChunkHeader().mDoneTimeStamp); 2085 releasedChunks = nextChunk; 2086 } 2087 MOZ_RELEASE_ASSERT(releasedChunkCount == i + 1); 2088 }); 2089 } 2090 printf("\n"); 2091 // Finally, the whole list of released chunks should have the exact same 2092 // timestamps as the initial list of "done" chunks. 2093 extantReleasedChunks = cm.GetExtantReleasedChunks(); 2094 for (unsigned i = 0; i < RandomReleaseChunkLoop; ++i) { 2095 MOZ_RELEASE_ASSERT(extantReleasedChunks, "Not enough released chunks"); 2096 MOZ_RELEASE_ASSERT(extantReleasedChunks->ChunkHeader().mDoneTimeStamp == 2097 chunksTimeStamps[i]); 2098 (void)std::exchange(extantReleasedChunks, 2099 extantReleasedChunks->ReleaseNext()); 2100 } 2101 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Too many released chunks"); 2102 2103 # ifdef DEBUG 2104 cm.DeregisteredFrom(chunkManagerRegisterer); 2105 # endif // DEBUG 2106 2107 printf("TestChunkManagerWithLocalLimit done\n"); 2108 } 2109 2110 static bool IsSameMetadata( 2111 const ProfileBufferControlledChunkManager::ChunkMetadata& a1, 2112 const ProfileBufferControlledChunkManager::ChunkMetadata& a2) { 2113 return a1.mDoneTimeStamp == a2.mDoneTimeStamp && 2114 a1.mBufferBytes == a2.mBufferBytes; 2115 }; 2116 2117 static bool IsSameUpdate( 2118 const ProfileBufferControlledChunkManager::Update& a1, 2119 const ProfileBufferControlledChunkManager::Update& a2) { 2120 // Final and not-an-update don't carry other data, so we can test these two 2121 // states first. 2122 if (a1.IsFinal() || a2.IsFinal()) { 2123 return a1.IsFinal() && a2.IsFinal(); 2124 } 2125 if (a1.IsNotUpdate() || a2.IsNotUpdate()) { 2126 return a1.IsNotUpdate() && a2.IsNotUpdate(); 2127 } 2128 2129 // Here, both are "normal" udpates, check member variables: 2130 2131 if (a1.UnreleasedBytes() != a2.UnreleasedBytes()) { 2132 return false; 2133 } 2134 if (a1.ReleasedBytes() != a2.ReleasedBytes()) { 2135 return false; 2136 } 2137 if (a1.OldestDoneTimeStamp() != a2.OldestDoneTimeStamp()) { 2138 return false; 2139 } 2140 if (a1.NewlyReleasedChunksRef().size() != 2141 a2.NewlyReleasedChunksRef().size()) { 2142 return false; 2143 } 2144 for (unsigned i = 0; i < a1.NewlyReleasedChunksRef().size(); ++i) { 2145 if (!IsSameMetadata(a1.NewlyReleasedChunksRef()[i], 2146 a2.NewlyReleasedChunksRef()[i])) { 2147 return false; 2148 } 2149 } 2150 return true; 2151 } 2152 2153 static void TestControlledChunkManagerUpdate() { 2154 printf("TestControlledChunkManagerUpdate...\n"); 2155 2156 using Update = ProfileBufferControlledChunkManager::Update; 2157 2158 // Default construction. 2159 Update update1; 2160 MOZ_RELEASE_ASSERT(update1.IsNotUpdate()); 2161 MOZ_RELEASE_ASSERT(!update1.IsFinal()); 2162 2163 // Clear an already-cleared update. 2164 update1.Clear(); 2165 MOZ_RELEASE_ASSERT(update1.IsNotUpdate()); 2166 MOZ_RELEASE_ASSERT(!update1.IsFinal()); 2167 2168 // Final construction with nullptr. 2169 const Update final(nullptr); 2170 MOZ_RELEASE_ASSERT(final.IsFinal()); 2171 MOZ_RELEASE_ASSERT(!final.IsNotUpdate()); 2172 2173 // Copy final to cleared. 2174 update1 = final; 2175 MOZ_RELEASE_ASSERT(update1.IsFinal()); 2176 MOZ_RELEASE_ASSERT(!update1.IsNotUpdate()); 2177 2178 // Copy final to final. 2179 update1 = final; 2180 MOZ_RELEASE_ASSERT(update1.IsFinal()); 2181 MOZ_RELEASE_ASSERT(!update1.IsNotUpdate()); 2182 2183 // Clear a final update. 2184 update1.Clear(); 2185 MOZ_RELEASE_ASSERT(update1.IsNotUpdate()); 2186 MOZ_RELEASE_ASSERT(!update1.IsFinal()); 2187 2188 // Move final to cleared. 2189 update1 = Update(nullptr); 2190 MOZ_RELEASE_ASSERT(update1.IsFinal()); 2191 MOZ_RELEASE_ASSERT(!update1.IsNotUpdate()); 2192 2193 // Move final to final. 2194 update1 = Update(nullptr); 2195 MOZ_RELEASE_ASSERT(update1.IsFinal()); 2196 MOZ_RELEASE_ASSERT(!update1.IsNotUpdate()); 2197 2198 // Move from not-an-update (effectively same as Clear). 2199 update1 = Update(); 2200 MOZ_RELEASE_ASSERT(update1.IsNotUpdate()); 2201 MOZ_RELEASE_ASSERT(!update1.IsFinal()); 2202 2203 auto CreateBiggerChunkAfter = [](const ProfileBufferChunk& aChunkToBeat) { 2204 while (TimeStamp::Now() <= aChunkToBeat.ChunkHeader().mDoneTimeStamp) { 2205 ::SleepMilli(1); 2206 } 2207 auto chunk = ProfileBufferChunk::Create(aChunkToBeat.BufferBytes() * 2); 2208 MOZ_RELEASE_ASSERT(!!chunk); 2209 MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= aChunkToBeat.BufferBytes() * 2); 2210 (void)chunk->ReserveInitialBlockAsTail(0); 2211 chunk->MarkDone(); 2212 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mDoneTimeStamp > 2213 aChunkToBeat.ChunkHeader().mDoneTimeStamp); 2214 return chunk; 2215 }; 2216 2217 update1 = Update(1, 2, nullptr, nullptr); 2218 2219 // Create initial update with 2 released chunks and 1 unreleased chunk. 2220 auto released = ProfileBufferChunk::Create(10); 2221 ProfileBufferChunk* c1 = released.get(); 2222 (void)c1->ReserveInitialBlockAsTail(0); 2223 c1->MarkDone(); 2224 2225 released->SetLast(CreateBiggerChunkAfter(*c1)); 2226 ProfileBufferChunk* c2 = c1->GetNext(); 2227 2228 auto unreleased = CreateBiggerChunkAfter(*c2); 2229 ProfileBufferChunk* c3 = unreleased.get(); 2230 2231 Update update2(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(), c1, 2232 c1); 2233 MOZ_RELEASE_ASSERT(IsSameUpdate( 2234 update2, 2235 Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(), 2236 c1->ChunkHeader().mDoneTimeStamp, 2237 {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()}, 2238 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}}))); 2239 // Check every field, this time only, after that we'll trust that the 2240 // `SameUpdate` test will be enough. 2241 MOZ_RELEASE_ASSERT(!update2.IsNotUpdate()); 2242 MOZ_RELEASE_ASSERT(!update2.IsFinal()); 2243 MOZ_RELEASE_ASSERT(update2.UnreleasedBytes() == c3->BufferBytes()); 2244 MOZ_RELEASE_ASSERT(update2.ReleasedBytes() == 2245 c1->BufferBytes() + c2->BufferBytes()); 2246 MOZ_RELEASE_ASSERT(update2.OldestDoneTimeStamp() == 2247 c1->ChunkHeader().mDoneTimeStamp); 2248 MOZ_RELEASE_ASSERT(update2.NewlyReleasedChunksRef().size() == 2); 2249 MOZ_RELEASE_ASSERT( 2250 IsSameMetadata(update2.NewlyReleasedChunksRef()[0], 2251 {c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()})); 2252 MOZ_RELEASE_ASSERT( 2253 IsSameMetadata(update2.NewlyReleasedChunksRef()[1], 2254 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()})); 2255 2256 // Fold into not-an-update. 2257 update1.Fold(std::move(update2)); 2258 MOZ_RELEASE_ASSERT(IsSameUpdate( 2259 update1, 2260 Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(), 2261 c1->ChunkHeader().mDoneTimeStamp, 2262 {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()}, 2263 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}}))); 2264 2265 // Pretend nothing happened. 2266 update2 = Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(), c1, 2267 nullptr); 2268 MOZ_RELEASE_ASSERT(IsSameUpdate( 2269 update2, Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(), 2270 c1->ChunkHeader().mDoneTimeStamp, {}))); 2271 update1.Fold(std::move(update2)); 2272 MOZ_RELEASE_ASSERT(IsSameUpdate( 2273 update1, 2274 Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(), 2275 c1->ChunkHeader().mDoneTimeStamp, 2276 {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()}, 2277 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}}))); 2278 2279 // Pretend there's a new unreleased chunk. 2280 c3->SetLast(CreateBiggerChunkAfter(*c3)); 2281 ProfileBufferChunk* c4 = c3->GetNext(); 2282 update2 = Update(c3->BufferBytes() + c4->BufferBytes(), 2283 c1->BufferBytes() + c2->BufferBytes(), c1, nullptr); 2284 MOZ_RELEASE_ASSERT( 2285 IsSameUpdate(update2, Update(c3->BufferBytes() + c4->BufferBytes(), 2286 c1->BufferBytes() + c2->BufferBytes(), 2287 c1->ChunkHeader().mDoneTimeStamp, {}))); 2288 update1.Fold(std::move(update2)); 2289 MOZ_RELEASE_ASSERT(IsSameUpdate( 2290 update1, 2291 Update(c3->BufferBytes() + c4->BufferBytes(), 2292 c1->BufferBytes() + c2->BufferBytes(), 2293 c1->ChunkHeader().mDoneTimeStamp, 2294 {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()}, 2295 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}}))); 2296 2297 // Pretend the first unreleased chunk c3 has been released. 2298 released->SetLast(std::exchange(unreleased, unreleased->ReleaseNext())); 2299 update2 = 2300 Update(c4->BufferBytes(), 2301 c1->BufferBytes() + c2->BufferBytes() + c3->BufferBytes(), c1, c3); 2302 MOZ_RELEASE_ASSERT(IsSameUpdate( 2303 update2, 2304 Update(c4->BufferBytes(), 2305 c1->BufferBytes() + c2->BufferBytes() + c3->BufferBytes(), 2306 c1->ChunkHeader().mDoneTimeStamp, 2307 {{c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()}}))); 2308 update1.Fold(std::move(update2)); 2309 MOZ_RELEASE_ASSERT(IsSameUpdate( 2310 update1, 2311 Update(c4->BufferBytes(), 2312 c1->BufferBytes() + c2->BufferBytes() + c3->BufferBytes(), 2313 c1->ChunkHeader().mDoneTimeStamp, 2314 {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()}, 2315 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}, 2316 {c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()}}))); 2317 2318 // Pretend c1 has been destroyed, so the oldest timestamp is now at c2. 2319 released = released->ReleaseNext(); 2320 c1 = nullptr; 2321 update2 = Update(c4->BufferBytes(), c2->BufferBytes() + c3->BufferBytes(), c2, 2322 nullptr); 2323 MOZ_RELEASE_ASSERT(IsSameUpdate( 2324 update2, Update(c4->BufferBytes(), c2->BufferBytes() + c3->BufferBytes(), 2325 c2->ChunkHeader().mDoneTimeStamp, {}))); 2326 update1.Fold(std::move(update2)); 2327 MOZ_RELEASE_ASSERT(IsSameUpdate( 2328 update1, 2329 Update(c4->BufferBytes(), c2->BufferBytes() + c3->BufferBytes(), 2330 c2->ChunkHeader().mDoneTimeStamp, 2331 {{c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}, 2332 {c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()}}))); 2333 2334 // Pretend c2 has been recycled to make unreleased c5, and c4 has been 2335 // released. 2336 auto recycled = std::exchange(released, released->ReleaseNext()); 2337 recycled->MarkRecycled(); 2338 (void)recycled->ReserveInitialBlockAsTail(0); 2339 recycled->MarkDone(); 2340 released->SetLast(std::move(unreleased)); 2341 unreleased = std::move(recycled); 2342 ProfileBufferChunk* c5 = c2; 2343 c2 = nullptr; 2344 update2 = 2345 Update(c5->BufferBytes(), c3->BufferBytes() + c4->BufferBytes(), c3, c4); 2346 MOZ_RELEASE_ASSERT(IsSameUpdate( 2347 update2, 2348 Update(c5->BufferBytes(), c3->BufferBytes() + c4->BufferBytes(), 2349 c3->ChunkHeader().mDoneTimeStamp, 2350 {{c4->ChunkHeader().mDoneTimeStamp, c4->BufferBytes()}}))); 2351 update1.Fold(std::move(update2)); 2352 MOZ_RELEASE_ASSERT(IsSameUpdate( 2353 update1, 2354 Update(c5->BufferBytes(), c3->BufferBytes() + c4->BufferBytes(), 2355 c3->ChunkHeader().mDoneTimeStamp, 2356 {{c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()}, 2357 {c4->ChunkHeader().mDoneTimeStamp, c4->BufferBytes()}}))); 2358 2359 // And send a final update. 2360 update1.Fold(Update(nullptr)); 2361 MOZ_RELEASE_ASSERT(update1.IsFinal()); 2362 MOZ_RELEASE_ASSERT(!update1.IsNotUpdate()); 2363 2364 printf("TestControlledChunkManagerUpdate done\n"); 2365 } 2366 2367 static void TestControlledChunkManagerWithLocalLimit() { 2368 printf("TestControlledChunkManagerWithLocalLimit...\n"); 2369 2370 // Construct a ProfileBufferChunkManagerWithLocalLimit with chunk of minimum 2371 // size >=100, up to 1000 bytes. 2372 constexpr ProfileBufferChunk::Length MaxTotalBytes = 1000; 2373 constexpr ProfileBufferChunk::Length ChunkMinBufferBytes = 100; 2374 ProfileBufferChunkManagerWithLocalLimit cmll{MaxTotalBytes, 2375 ChunkMinBufferBytes}; 2376 2377 // Reference to chunk manager base class. 2378 ProfileBufferChunkManager& cm = cmll; 2379 2380 // Reference to controlled chunk manager base class. 2381 ProfileBufferControlledChunkManager& ccm = cmll; 2382 2383 # ifdef DEBUG 2384 const char* chunkManagerRegisterer = 2385 "TestControlledChunkManagerWithLocalLimit"; 2386 cm.RegisteredWith(chunkManagerRegisterer); 2387 # endif // DEBUG 2388 2389 MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == MaxTotalBytes, 2390 "Max total size should be exactly as given"); 2391 2392 unsigned destroyedChunks = 0; 2393 unsigned destroyedBytes = 0; 2394 cm.SetChunkDestroyedCallback([&](const ProfileBufferChunk& aChunks) { 2395 for (const ProfileBufferChunk* chunk = &aChunks; chunk; 2396 chunk = chunk->GetNext()) { 2397 destroyedChunks += 1; 2398 destroyedBytes += chunk->BufferBytes(); 2399 } 2400 }); 2401 2402 using Update = ProfileBufferControlledChunkManager::Update; 2403 unsigned updateCount = 0; 2404 ProfileBufferControlledChunkManager::Update update; 2405 MOZ_RELEASE_ASSERT(update.IsNotUpdate()); 2406 auto updateCallback = [&](Update&& aUpdate) { 2407 ++updateCount; 2408 update.Fold(std::move(aUpdate)); 2409 }; 2410 ccm.SetUpdateCallback(updateCallback); 2411 MOZ_RELEASE_ASSERT(updateCount == 1, 2412 "SetUpdateCallback should have triggered an update"); 2413 MOZ_RELEASE_ASSERT(IsSameUpdate(update, Update(0, 0, TimeStamp{}, {}))); 2414 updateCount = 0; 2415 update.Clear(); 2416 2417 UniquePtr<ProfileBufferChunk> extantReleasedChunks = 2418 cm.GetExtantReleasedChunks(); 2419 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)"); 2420 MOZ_RELEASE_ASSERT(updateCount == 1, 2421 "GetExtantReleasedChunks should have triggered an update"); 2422 MOZ_RELEASE_ASSERT(IsSameUpdate(update, Update(0, 0, TimeStamp{}, {}))); 2423 updateCount = 0; 2424 update.Clear(); 2425 2426 // First request. 2427 UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk(); 2428 MOZ_RELEASE_ASSERT(!!chunk, 2429 "First chunk immediate request should always work"); 2430 const auto chunkActualBufferBytes = chunk->BufferBytes(); 2431 MOZ_RELEASE_ASSERT(updateCount == 1, 2432 "GetChunk should have triggered an update"); 2433 MOZ_RELEASE_ASSERT( 2434 IsSameUpdate(update, Update(chunk->BufferBytes(), 0, TimeStamp{}, {}))); 2435 updateCount = 0; 2436 update.Clear(); 2437 2438 extantReleasedChunks = cm.GetExtantReleasedChunks(); 2439 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)"); 2440 MOZ_RELEASE_ASSERT(updateCount == 1, 2441 "GetExtantReleasedChunks should have triggered an update"); 2442 MOZ_RELEASE_ASSERT( 2443 IsSameUpdate(update, Update(chunk->BufferBytes(), 0, TimeStamp{}, {}))); 2444 updateCount = 0; 2445 update.Clear(); 2446 2447 // For this test, we need to be able to get at least 2 chunks without hitting 2448 // the limit. (If this failed, it wouldn't necessary be a problem with 2449 // ProfileBufferChunkManagerWithLocalLimit, fiddle with constants at the top 2450 // of this test.) 2451 MOZ_RELEASE_ASSERT(chunkActualBufferBytes < 2 * MaxTotalBytes); 2452 2453 ProfileBufferChunk::Length previousUnreleasedBytes = chunk->BufferBytes(); 2454 ProfileBufferChunk::Length previousReleasedBytes = 0; 2455 TimeStamp previousOldestDoneTimeStamp; 2456 2457 // We will do enough loops to go through the maximum size a number of times. 2458 const unsigned Rollovers = 3; 2459 const unsigned Loops = Rollovers * MaxTotalBytes / chunkActualBufferBytes; 2460 for (unsigned i = 0; i < Loops; ++i) { 2461 // Add some data to the chunk. 2462 const ProfileBufferIndex index = 2463 ProfileBufferIndex(chunkActualBufferBytes) * i + 1; 2464 chunk->SetRangeStart(index); 2465 (void)chunk->ReserveInitialBlockAsTail(1); 2466 (void)chunk->ReserveBlock(2); 2467 2468 // Request a new chunk. 2469 UniquePtr<ProfileBufferChunk> newChunk; 2470 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) { 2471 newChunk = std::move(aChunk); 2472 }); 2473 MOZ_RELEASE_ASSERT(updateCount == 0, 2474 "RequestChunk() shouldn't have triggered an update"); 2475 cm.FulfillChunkRequests(); 2476 MOZ_RELEASE_ASSERT(!!newChunk, "Chunk request should always work"); 2477 MOZ_RELEASE_ASSERT(newChunk->BufferBytes() == chunkActualBufferBytes, 2478 "Unexpected chunk size"); 2479 MOZ_RELEASE_ASSERT(!newChunk->GetNext(), "There should only be one chunk"); 2480 2481 MOZ_RELEASE_ASSERT(updateCount == 1, 2482 "FulfillChunkRequests() after a request should have " 2483 "triggered an update"); 2484 MOZ_RELEASE_ASSERT(!update.IsFinal()); 2485 MOZ_RELEASE_ASSERT(!update.IsNotUpdate()); 2486 MOZ_RELEASE_ASSERT(update.UnreleasedBytes() == 2487 previousUnreleasedBytes + newChunk->BufferBytes()); 2488 previousUnreleasedBytes = update.UnreleasedBytes(); 2489 MOZ_RELEASE_ASSERT(update.ReleasedBytes() <= previousReleasedBytes); 2490 previousReleasedBytes = update.ReleasedBytes(); 2491 MOZ_RELEASE_ASSERT(previousOldestDoneTimeStamp.IsNull() || 2492 update.OldestDoneTimeStamp() >= 2493 previousOldestDoneTimeStamp); 2494 previousOldestDoneTimeStamp = update.OldestDoneTimeStamp(); 2495 MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().empty()); 2496 updateCount = 0; 2497 update.Clear(); 2498 2499 // Make sure the "Done" timestamp below cannot be the same as from the 2500 // previous loop. 2501 const TimeStamp now = TimeStamp::Now(); 2502 while (TimeStamp::Now() == now) { 2503 ::SleepMilli(1); 2504 } 2505 2506 // Mark previous chunk done and release it. 2507 WaitUntilTimeStampChanges(); // Force "done" timestamp to change. 2508 chunk->MarkDone(); 2509 const auto doneTimeStamp = chunk->ChunkHeader().mDoneTimeStamp; 2510 const auto bufferBytes = chunk->BufferBytes(); 2511 cm.ReleaseChunk(std::move(chunk)); 2512 2513 MOZ_RELEASE_ASSERT(updateCount == 1, 2514 "ReleaseChunk() should have triggered an update"); 2515 MOZ_RELEASE_ASSERT(!update.IsFinal()); 2516 MOZ_RELEASE_ASSERT(!update.IsNotUpdate()); 2517 MOZ_RELEASE_ASSERT(update.UnreleasedBytes() == 2518 previousUnreleasedBytes - bufferBytes); 2519 previousUnreleasedBytes = update.UnreleasedBytes(); 2520 MOZ_RELEASE_ASSERT(update.ReleasedBytes() == 2521 previousReleasedBytes + bufferBytes); 2522 previousReleasedBytes = update.ReleasedBytes(); 2523 MOZ_RELEASE_ASSERT(previousOldestDoneTimeStamp.IsNull() || 2524 update.OldestDoneTimeStamp() >= 2525 previousOldestDoneTimeStamp); 2526 previousOldestDoneTimeStamp = update.OldestDoneTimeStamp(); 2527 MOZ_RELEASE_ASSERT(update.OldestDoneTimeStamp() <= doneTimeStamp); 2528 MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().size() == 1); 2529 MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef()[0].mDoneTimeStamp == 2530 doneTimeStamp); 2531 MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef()[0].mBufferBytes == 2532 bufferBytes); 2533 updateCount = 0; 2534 update.Clear(); 2535 2536 // And cycle to the new chunk. 2537 chunk = std::move(newChunk); 2538 } 2539 2540 // Enough testing! Clean-up. 2541 (void)chunk->ReserveInitialBlockAsTail(0); 2542 chunk->MarkDone(); 2543 cm.ForgetUnreleasedChunks(); 2544 MOZ_RELEASE_ASSERT( 2545 updateCount == 1, 2546 "ForgetUnreleasedChunks() should have triggered an update"); 2547 MOZ_RELEASE_ASSERT(!update.IsFinal()); 2548 MOZ_RELEASE_ASSERT(!update.IsNotUpdate()); 2549 MOZ_RELEASE_ASSERT(update.UnreleasedBytes() == 0); 2550 MOZ_RELEASE_ASSERT(update.ReleasedBytes() == previousReleasedBytes); 2551 MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().empty() == 1); 2552 updateCount = 0; 2553 update.Clear(); 2554 2555 ccm.SetUpdateCallback({}); 2556 MOZ_RELEASE_ASSERT(updateCount == 1, 2557 "SetUpdateCallback({}) should have triggered an update"); 2558 MOZ_RELEASE_ASSERT(update.IsFinal()); 2559 2560 # ifdef DEBUG 2561 cm.DeregisteredFrom(chunkManagerRegisterer); 2562 # endif // DEBUG 2563 2564 printf("TestControlledChunkManagerWithLocalLimit done\n"); 2565 } 2566 2567 # define VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( \ 2568 aProfileChunkedBuffer, aStart, aEnd, aPushed, aCleared, aFailed) \ 2569 { \ 2570 ProfileChunkedBuffer::State state = (aProfileChunkedBuffer).GetState(); \ 2571 MOZ_RELEASE_ASSERT(state.mRangeStart == (aStart)); \ 2572 MOZ_RELEASE_ASSERT(state.mRangeEnd == (aEnd)); \ 2573 MOZ_RELEASE_ASSERT(state.mPushedBlockCount == (aPushed)); \ 2574 MOZ_RELEASE_ASSERT(state.mClearedBlockCount == (aCleared)); \ 2575 MOZ_RELEASE_ASSERT(state.mFailedPutBytes == (aFailed)); \ 2576 } 2577 2578 static void TestChunkedBuffer() { 2579 printf("TestChunkedBuffer...\n"); 2580 2581 ProfileBufferBlockIndex blockIndex; 2582 MOZ_RELEASE_ASSERT(!blockIndex); 2583 MOZ_RELEASE_ASSERT(blockIndex == nullptr); 2584 2585 // Create an out-of-session ProfileChunkedBuffer. 2586 ProfileChunkedBuffer cb(ProfileChunkedBuffer::ThreadSafety::WithMutex); 2587 2588 MOZ_RELEASE_ASSERT(cb.BufferLength().isNothing()); 2589 2590 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0); 2591 2592 int result = 0; 2593 result = cb.ReserveAndPut( 2594 []() { 2595 MOZ_RELEASE_ASSERT(false); 2596 return 1; 2597 }, 2598 [](Maybe<ProfileBufferEntryWriter>& aEW) { return aEW ? 2 : 3; }); 2599 MOZ_RELEASE_ASSERT(result == 3); 2600 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0); 2601 2602 result = 0; 2603 result = cb.Put( 2604 1, [](Maybe<ProfileBufferEntryWriter>& aEW) { return aEW ? 1 : 2; }); 2605 MOZ_RELEASE_ASSERT(result == 2); 2606 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0); 2607 2608 blockIndex = cb.PutFrom(&result, 1); 2609 MOZ_RELEASE_ASSERT(!blockIndex); 2610 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0); 2611 2612 blockIndex = cb.PutObjects(123, result, "hello"); 2613 MOZ_RELEASE_ASSERT(!blockIndex); 2614 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0); 2615 2616 blockIndex = cb.PutObject(123); 2617 MOZ_RELEASE_ASSERT(!blockIndex); 2618 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0); 2619 2620 auto chunks = cb.GetAllChunks(); 2621 static_assert(std::is_same_v<decltype(chunks), UniquePtr<ProfileBufferChunk>>, 2622 "ProfileChunkedBuffer::GetAllChunks() should return a " 2623 "UniquePtr<ProfileBufferChunk>"); 2624 MOZ_RELEASE_ASSERT(!chunks, "Expected no chunks when out-of-session"); 2625 2626 bool ran = false; 2627 result = 0; 2628 result = cb.Read([&](ProfileChunkedBuffer::Reader* aReader) { 2629 ran = true; 2630 MOZ_RELEASE_ASSERT(!aReader); 2631 return 3; 2632 }); 2633 MOZ_RELEASE_ASSERT(ran); 2634 MOZ_RELEASE_ASSERT(result == 3); 2635 2636 cb.ReadEach([](ProfileBufferEntryReader&) { MOZ_RELEASE_ASSERT(false); }); 2637 2638 result = 0; 2639 result = cb.ReadAt(nullptr, [](Maybe<ProfileBufferEntryReader>&& er) { 2640 MOZ_RELEASE_ASSERT(er.isNothing()); 2641 return 4; 2642 }); 2643 MOZ_RELEASE_ASSERT(result == 4); 2644 2645 // Use ProfileBufferChunkManagerWithLocalLimit, which will give away 2646 // ProfileBufferChunks that can contain 128 bytes, using up to 1KB of memory 2647 // (including usable 128 bytes and headers). 2648 constexpr size_t bufferMaxSize = 1024; 2649 constexpr ProfileChunkedBuffer::Length chunkMinSize = 128; 2650 ProfileBufferChunkManagerWithLocalLimit cm(bufferMaxSize, chunkMinSize); 2651 cb.SetChunkManager(cm); 2652 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0); 2653 2654 // Let the chunk manager fulfill the initial request for an extra chunk. 2655 cm.FulfillChunkRequests(); 2656 2657 MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == bufferMaxSize); 2658 MOZ_RELEASE_ASSERT(cb.BufferLength().isSome()); 2659 MOZ_RELEASE_ASSERT(*cb.BufferLength() == bufferMaxSize); 2660 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0); 2661 2662 // Write an int with the main `ReserveAndPut` function. 2663 const int test = 123; 2664 ran = false; 2665 blockIndex = nullptr; 2666 bool success = cb.ReserveAndPut( 2667 []() { return sizeof(test); }, 2668 [&](Maybe<ProfileBufferEntryWriter>& aEW) { 2669 ran = true; 2670 if (!aEW) { 2671 return false; 2672 } 2673 blockIndex = aEW->CurrentBlockIndex(); 2674 MOZ_RELEASE_ASSERT(aEW->RemainingBytes() == sizeof(test)); 2675 aEW->WriteObject(test); 2676 MOZ_RELEASE_ASSERT(aEW->RemainingBytes() == 0); 2677 return true; 2678 }); 2679 MOZ_RELEASE_ASSERT(ran); 2680 MOZ_RELEASE_ASSERT(success); 2681 MOZ_RELEASE_ASSERT(blockIndex.ConvertToProfileBufferIndex() == 1); 2682 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 2683 cb, 1, 1 + ULEB128Size(sizeof(test)) + sizeof(test), 1, 0, 0); 2684 2685 ran = false; 2686 result = 0; 2687 result = cb.Read([&](ProfileChunkedBuffer::Reader* aReader) { 2688 ran = true; 2689 MOZ_RELEASE_ASSERT(!!aReader); 2690 // begin() and end() should be at the range edges (verified above). 2691 MOZ_RELEASE_ASSERT( 2692 aReader->begin().CurrentBlockIndex().ConvertToProfileBufferIndex() == 2693 1); 2694 MOZ_RELEASE_ASSERT( 2695 aReader->end().CurrentBlockIndex().ConvertToProfileBufferIndex() == 0); 2696 // Null ProfileBufferBlockIndex clamped to the beginning. 2697 MOZ_RELEASE_ASSERT(aReader->At(nullptr) == aReader->begin()); 2698 MOZ_RELEASE_ASSERT(aReader->At(blockIndex) == aReader->begin()); 2699 // At(begin) same as begin(). 2700 MOZ_RELEASE_ASSERT(aReader->At(aReader->begin().CurrentBlockIndex()) == 2701 aReader->begin()); 2702 // At(past block) same as end(). 2703 MOZ_RELEASE_ASSERT( 2704 aReader->At(ProfileBufferBlockIndex::CreateFromProfileBufferIndex( 2705 1 + 1 + sizeof(test))) == aReader->end()); 2706 2707 size_t read = 0; 2708 aReader->ForEach([&](ProfileBufferEntryReader& er) { 2709 ++read; 2710 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(test)); 2711 const auto value = er.ReadObject<decltype(test)>(); 2712 MOZ_RELEASE_ASSERT(value == test); 2713 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0); 2714 }); 2715 MOZ_RELEASE_ASSERT(read == 1); 2716 2717 read = 0; 2718 for (auto er : *aReader) { 2719 static_assert(std::is_same_v<decltype(er), ProfileBufferEntryReader>, 2720 "ProfileChunkedBuffer::Reader range-for should produce " 2721 "ProfileBufferEntryReader objects"); 2722 ++read; 2723 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(test)); 2724 const auto value = er.ReadObject<decltype(test)>(); 2725 MOZ_RELEASE_ASSERT(value == test); 2726 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0); 2727 }; 2728 MOZ_RELEASE_ASSERT(read == 1); 2729 return 5; 2730 }); 2731 MOZ_RELEASE_ASSERT(ran); 2732 MOZ_RELEASE_ASSERT(result == 5); 2733 2734 // Read the int directly from the ProfileChunkedBuffer, without block index. 2735 size_t read = 0; 2736 cb.ReadEach([&](ProfileBufferEntryReader& er) { 2737 ++read; 2738 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(test)); 2739 const auto value = er.ReadObject<decltype(test)>(); 2740 MOZ_RELEASE_ASSERT(value == test); 2741 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0); 2742 }); 2743 MOZ_RELEASE_ASSERT(read == 1); 2744 2745 // Read the int directly from the ProfileChunkedBuffer, with block index. 2746 read = 0; 2747 blockIndex = nullptr; 2748 cb.ReadEach( 2749 [&](ProfileBufferEntryReader& er, ProfileBufferBlockIndex aBlockIndex) { 2750 ++read; 2751 MOZ_RELEASE_ASSERT(!!aBlockIndex); 2752 MOZ_RELEASE_ASSERT(!blockIndex); 2753 blockIndex = aBlockIndex; 2754 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(test)); 2755 const auto value = er.ReadObject<decltype(test)>(); 2756 MOZ_RELEASE_ASSERT(value == test); 2757 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0); 2758 }); 2759 MOZ_RELEASE_ASSERT(read == 1); 2760 MOZ_RELEASE_ASSERT(!!blockIndex); 2761 MOZ_RELEASE_ASSERT(blockIndex != nullptr); 2762 2763 // Read the int from its block index. 2764 read = 0; 2765 result = 0; 2766 result = cb.ReadAt(blockIndex, [&](Maybe<ProfileBufferEntryReader>&& er) { 2767 ++read; 2768 MOZ_RELEASE_ASSERT(er.isSome()); 2769 MOZ_RELEASE_ASSERT(er->CurrentBlockIndex() == blockIndex); 2770 MOZ_RELEASE_ASSERT(!er->NextBlockIndex()); 2771 MOZ_RELEASE_ASSERT(er->RemainingBytes() == sizeof(test)); 2772 const auto value = er->ReadObject<decltype(test)>(); 2773 MOZ_RELEASE_ASSERT(value == test); 2774 MOZ_RELEASE_ASSERT(er->RemainingBytes() == 0); 2775 return 6; 2776 }); 2777 MOZ_RELEASE_ASSERT(result == 6); 2778 MOZ_RELEASE_ASSERT(read == 1); 2779 2780 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(ProfileBufferIndex{})); 2781 MOZ_RELEASE_ASSERT( 2782 cb.IsIndexInCurrentChunk(blockIndex.ConvertToProfileBufferIndex())); 2783 MOZ_RELEASE_ASSERT(cb.IsIndexInCurrentChunk(cb.GetState().mRangeEnd - 1)); 2784 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(cb.GetState().mRangeEnd)); 2785 2786 // No changes after reads. 2787 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 2788 cb, 1, 1 + ULEB128Size(sizeof(test)) + sizeof(test), 1, 0, 0); 2789 2790 // Steal the underlying ProfileBufferChunks from the ProfileChunkedBuffer. 2791 chunks = cb.GetAllChunks(); 2792 MOZ_RELEASE_ASSERT(!!chunks, "Expected at least one chunk"); 2793 MOZ_RELEASE_ASSERT(!!chunks->GetNext(), "Expected two chunks"); 2794 MOZ_RELEASE_ASSERT(!chunks->GetNext()->GetNext(), "Expected only two chunks"); 2795 const ProfileChunkedBuffer::Length chunkActualSize = chunks->BufferBytes(); 2796 MOZ_RELEASE_ASSERT(chunkActualSize >= chunkMinSize); 2797 MOZ_RELEASE_ASSERT(chunks->RangeStart() == 1); 2798 MOZ_RELEASE_ASSERT(chunks->OffsetFirstBlock() == 0); 2799 MOZ_RELEASE_ASSERT(chunks->OffsetPastLastBlock() == 1 + sizeof(test)); 2800 2801 // GetAllChunks() should have advanced the index one full chunk forward. 2802 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1 + chunkActualSize, 2803 1 + chunkActualSize, 1, 0, 0); 2804 2805 // Nothing more to read from the now-empty ProfileChunkedBuffer. 2806 cb.ReadEach([](ProfileBufferEntryReader&) { MOZ_RELEASE_ASSERT(false); }); 2807 cb.ReadEach([](ProfileBufferEntryReader&, ProfileBufferBlockIndex) { 2808 MOZ_RELEASE_ASSERT(false); 2809 }); 2810 result = 0; 2811 result = cb.ReadAt(nullptr, [](Maybe<ProfileBufferEntryReader>&& er) { 2812 MOZ_RELEASE_ASSERT(er.isNothing()); 2813 return 7; 2814 }); 2815 MOZ_RELEASE_ASSERT(result == 7); 2816 2817 // Read the int from the stolen chunks. 2818 read = 0; 2819 ProfileChunkedBuffer::ReadEach( 2820 chunks.get(), nullptr, 2821 [&](ProfileBufferEntryReader& er, ProfileBufferBlockIndex aBlockIndex) { 2822 ++read; 2823 MOZ_RELEASE_ASSERT(aBlockIndex == blockIndex); 2824 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(test)); 2825 const auto value = er.ReadObject<decltype(test)>(); 2826 MOZ_RELEASE_ASSERT(value == test); 2827 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0); 2828 }); 2829 MOZ_RELEASE_ASSERT(read == 1); 2830 2831 // No changes after reads. 2832 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1 + chunkActualSize, 2833 1 + chunkActualSize, 1, 0, 0); 2834 2835 // Write lots of numbers (by memcpy), which should trigger Chunk destructions. 2836 ProfileBufferBlockIndex firstBlockIndex; 2837 MOZ_RELEASE_ASSERT(!firstBlockIndex); 2838 ProfileBufferBlockIndex lastBlockIndex; 2839 MOZ_RELEASE_ASSERT(!lastBlockIndex); 2840 const size_t lots = 2 * bufferMaxSize / (1 + sizeof(int)); 2841 for (size_t i = 1; i < lots; ++i) { 2842 ProfileBufferBlockIndex blockIndex = cb.PutFrom(&i, sizeof(i)); 2843 MOZ_RELEASE_ASSERT(!!blockIndex); 2844 MOZ_RELEASE_ASSERT(blockIndex > firstBlockIndex); 2845 if (!firstBlockIndex) { 2846 firstBlockIndex = blockIndex; 2847 } 2848 MOZ_RELEASE_ASSERT(blockIndex > lastBlockIndex); 2849 lastBlockIndex = blockIndex; 2850 } 2851 2852 ProfileChunkedBuffer::State stateAfterPuts = cb.GetState(); 2853 ProfileBufferIndex startAfterPuts = stateAfterPuts.mRangeStart; 2854 MOZ_RELEASE_ASSERT(startAfterPuts > 1 + chunkActualSize); 2855 ProfileBufferIndex endAfterPuts = stateAfterPuts.mRangeEnd; 2856 MOZ_RELEASE_ASSERT(endAfterPuts > startAfterPuts); 2857 uint64_t pushedAfterPuts = stateAfterPuts.mPushedBlockCount; 2858 MOZ_RELEASE_ASSERT(pushedAfterPuts > 0); 2859 uint64_t clearedAfterPuts = stateAfterPuts.mClearedBlockCount; 2860 MOZ_RELEASE_ASSERT(clearedAfterPuts > 0); 2861 MOZ_RELEASE_ASSERT(stateAfterPuts.mFailedPutBytes == 0); 2862 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(ProfileBufferIndex{})); 2863 MOZ_RELEASE_ASSERT( 2864 !cb.IsIndexInCurrentChunk(blockIndex.ConvertToProfileBufferIndex())); 2865 MOZ_RELEASE_ASSERT( 2866 !cb.IsIndexInCurrentChunk(firstBlockIndex.ConvertToProfileBufferIndex())); 2867 2868 // Read extant numbers, which should at least follow each other. 2869 read = 0; 2870 size_t i = 0; 2871 cb.ReadEach( 2872 [&](ProfileBufferEntryReader& er, ProfileBufferBlockIndex aBlockIndex) { 2873 ++read; 2874 MOZ_RELEASE_ASSERT(!!aBlockIndex); 2875 MOZ_RELEASE_ASSERT(aBlockIndex > firstBlockIndex); 2876 MOZ_RELEASE_ASSERT(aBlockIndex <= lastBlockIndex); 2877 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(size_t)); 2878 const auto value = er.ReadObject<size_t>(); 2879 if (i == 0) { 2880 i = value; 2881 } else { 2882 MOZ_RELEASE_ASSERT(value == ++i); 2883 } 2884 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0); 2885 }); 2886 MOZ_RELEASE_ASSERT(read != 0); 2887 MOZ_RELEASE_ASSERT(read < lots); 2888 2889 // Read first extant number. 2890 read = 0; 2891 i = 0; 2892 blockIndex = nullptr; 2893 success = 2894 cb.ReadAt(firstBlockIndex, [&](Maybe<ProfileBufferEntryReader>&& er) { 2895 MOZ_ASSERT(er.isSome()); 2896 ++read; 2897 MOZ_RELEASE_ASSERT(er->CurrentBlockIndex() > firstBlockIndex); 2898 MOZ_RELEASE_ASSERT(!!er->NextBlockIndex()); 2899 MOZ_RELEASE_ASSERT(er->NextBlockIndex() > firstBlockIndex); 2900 MOZ_RELEASE_ASSERT(er->NextBlockIndex() < lastBlockIndex); 2901 blockIndex = er->NextBlockIndex(); 2902 MOZ_RELEASE_ASSERT(er->RemainingBytes() == sizeof(size_t)); 2903 const auto value = er->ReadObject<size_t>(); 2904 MOZ_RELEASE_ASSERT(i == 0); 2905 i = value; 2906 MOZ_RELEASE_ASSERT(er->RemainingBytes() == 0); 2907 return 7; 2908 }); 2909 MOZ_RELEASE_ASSERT(success); 2910 MOZ_RELEASE_ASSERT(read == 1); 2911 // Read other extant numbers one by one. 2912 do { 2913 bool success = 2914 cb.ReadAt(blockIndex, [&](Maybe<ProfileBufferEntryReader>&& er) { 2915 MOZ_ASSERT(er.isSome()); 2916 ++read; 2917 MOZ_RELEASE_ASSERT(er->CurrentBlockIndex() == blockIndex); 2918 MOZ_RELEASE_ASSERT(!er->NextBlockIndex() || 2919 er->NextBlockIndex() > blockIndex); 2920 MOZ_RELEASE_ASSERT(!er->NextBlockIndex() || 2921 er->NextBlockIndex() > firstBlockIndex); 2922 MOZ_RELEASE_ASSERT(!er->NextBlockIndex() || 2923 er->NextBlockIndex() <= lastBlockIndex); 2924 MOZ_RELEASE_ASSERT(er->NextBlockIndex() 2925 ? blockIndex < lastBlockIndex 2926 : blockIndex == lastBlockIndex, 2927 "er->NextBlockIndex() should only be null when " 2928 "blockIndex is at the last block"); 2929 blockIndex = er->NextBlockIndex(); 2930 MOZ_RELEASE_ASSERT(er->RemainingBytes() == sizeof(size_t)); 2931 const auto value = er->ReadObject<size_t>(); 2932 MOZ_RELEASE_ASSERT(value == ++i); 2933 MOZ_RELEASE_ASSERT(er->RemainingBytes() == 0); 2934 return true; 2935 }); 2936 MOZ_RELEASE_ASSERT(success); 2937 } while (blockIndex); 2938 MOZ_RELEASE_ASSERT(read > 1); 2939 2940 // No changes after reads. 2941 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 2942 cb, startAfterPuts, endAfterPuts, pushedAfterPuts, clearedAfterPuts, 0); 2943 2944 # ifdef DEBUG 2945 // cb.Dump(); 2946 # endif 2947 2948 cb.Clear(); 2949 2950 # ifdef DEBUG 2951 // cb.Dump(); 2952 # endif 2953 2954 ProfileChunkedBuffer::State stateAfterClear = cb.GetState(); 2955 ProfileBufferIndex startAfterClear = stateAfterClear.mRangeStart; 2956 MOZ_RELEASE_ASSERT(startAfterClear > startAfterPuts); 2957 ProfileBufferIndex endAfterClear = stateAfterClear.mRangeEnd; 2958 MOZ_RELEASE_ASSERT(endAfterClear == startAfterClear); 2959 MOZ_RELEASE_ASSERT(stateAfterClear.mPushedBlockCount == 0); 2960 MOZ_RELEASE_ASSERT(stateAfterClear.mClearedBlockCount == 0); 2961 MOZ_RELEASE_ASSERT(stateAfterClear.mFailedPutBytes == 0); 2962 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(ProfileBufferIndex{})); 2963 MOZ_RELEASE_ASSERT( 2964 !cb.IsIndexInCurrentChunk(blockIndex.ConvertToProfileBufferIndex())); 2965 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(stateAfterClear.mRangeEnd - 1)); 2966 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(stateAfterClear.mRangeEnd)); 2967 2968 // Start writer threads. 2969 constexpr int ThreadCount = 32; 2970 std::thread threads[ThreadCount]; 2971 for (int threadNo = 0; threadNo < ThreadCount; ++threadNo) { 2972 threads[threadNo] = std::thread( 2973 [&](int aThreadNo) { 2974 ::SleepMilli(1); 2975 constexpr int pushCount = 1024; 2976 for (int push = 0; push < pushCount; ++push) { 2977 // Reserve as many bytes as the thread number (but at least enough 2978 // to store an int), and write an increasing int. 2979 const bool success = 2980 cb.Put(std::max(aThreadNo, int(sizeof(push))), 2981 [&](Maybe<ProfileBufferEntryWriter>& aEW) { 2982 if (!aEW) { 2983 return false; 2984 } 2985 aEW->WriteObject(aThreadNo * 1000000 + push); 2986 // Advance writer to the end. 2987 for (size_t r = aEW->RemainingBytes(); r != 0; --r) { 2988 aEW->WriteObject<char>('_'); 2989 } 2990 return true; 2991 }); 2992 MOZ_RELEASE_ASSERT(success); 2993 } 2994 }, 2995 threadNo); 2996 } 2997 2998 // Wait for all writer threads to die. 2999 for (auto&& thread : threads) { 3000 thread.join(); 3001 } 3002 3003 # ifdef DEBUG 3004 // cb.Dump(); 3005 # endif 3006 3007 ProfileChunkedBuffer::State stateAfterMTPuts = cb.GetState(); 3008 ProfileBufferIndex startAfterMTPuts = stateAfterMTPuts.mRangeStart; 3009 MOZ_RELEASE_ASSERT(startAfterMTPuts > startAfterClear); 3010 ProfileBufferIndex endAfterMTPuts = stateAfterMTPuts.mRangeEnd; 3011 MOZ_RELEASE_ASSERT(endAfterMTPuts > startAfterMTPuts); 3012 MOZ_RELEASE_ASSERT(stateAfterMTPuts.mPushedBlockCount > 0); 3013 MOZ_RELEASE_ASSERT(stateAfterMTPuts.mClearedBlockCount > 0); 3014 MOZ_RELEASE_ASSERT(stateAfterMTPuts.mFailedPutBytes == 0); 3015 3016 // Reset to out-of-session. 3017 cb.ResetChunkManager(); 3018 3019 ProfileChunkedBuffer::State stateAfterReset = cb.GetState(); 3020 ProfileBufferIndex startAfterReset = stateAfterReset.mRangeStart; 3021 MOZ_RELEASE_ASSERT(startAfterReset == endAfterMTPuts); 3022 ProfileBufferIndex endAfterReset = stateAfterReset.mRangeEnd; 3023 MOZ_RELEASE_ASSERT(endAfterReset == startAfterReset); 3024 MOZ_RELEASE_ASSERT(stateAfterReset.mPushedBlockCount == 0); 3025 MOZ_RELEASE_ASSERT(stateAfterReset.mClearedBlockCount == 0); 3026 MOZ_RELEASE_ASSERT(stateAfterReset.mFailedPutBytes == 0); 3027 3028 success = cb.ReserveAndPut( 3029 []() { 3030 MOZ_RELEASE_ASSERT(false); 3031 return 1; 3032 }, 3033 [](Maybe<ProfileBufferEntryWriter>& aEW) { return !!aEW; }); 3034 MOZ_RELEASE_ASSERT(!success); 3035 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset, 3036 0, 0, 0); 3037 3038 success = 3039 cb.Put(1, [](Maybe<ProfileBufferEntryWriter>& aEW) { return !!aEW; }); 3040 MOZ_RELEASE_ASSERT(!success); 3041 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset, 3042 0, 0, 0); 3043 3044 blockIndex = cb.PutFrom(&success, 1); 3045 MOZ_RELEASE_ASSERT(!blockIndex); 3046 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset, 3047 0, 0, 0); 3048 3049 blockIndex = cb.PutObjects(123, success, "hello"); 3050 MOZ_RELEASE_ASSERT(!blockIndex); 3051 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset, 3052 0, 0, 0); 3053 3054 blockIndex = cb.PutObject(123); 3055 MOZ_RELEASE_ASSERT(!blockIndex); 3056 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset, 3057 0, 0, 0); 3058 3059 chunks = cb.GetAllChunks(); 3060 MOZ_RELEASE_ASSERT(!chunks, "Expected no chunks when out-of-session"); 3061 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset, 3062 0, 0, 0); 3063 3064 cb.ReadEach([](ProfileBufferEntryReader&) { MOZ_RELEASE_ASSERT(false); }); 3065 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset, 3066 0, 0, 0); 3067 3068 success = cb.ReadAt(nullptr, [](Maybe<ProfileBufferEntryReader>&& er) { 3069 MOZ_RELEASE_ASSERT(er.isNothing()); 3070 return true; 3071 }); 3072 MOZ_RELEASE_ASSERT(success); 3073 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset, 3074 0, 0, 0); 3075 3076 printf("TestChunkedBuffer done\n"); 3077 } 3078 3079 static void TestChunkedBufferSingle() { 3080 printf("TestChunkedBufferSingle...\n"); 3081 3082 constexpr ProfileChunkedBuffer::Length chunkMinSize = 128; 3083 3084 // Create a ProfileChunkedBuffer that will own&use a 3085 // ProfileBufferChunkManagerSingle, which will give away one 3086 // ProfileBufferChunk that can contain 128 bytes. 3087 ProfileChunkedBuffer cbSingle( 3088 ProfileChunkedBuffer::ThreadSafety::WithoutMutex, 3089 MakeUnique<ProfileBufferChunkManagerSingle>(chunkMinSize)); 3090 3091 MOZ_RELEASE_ASSERT(cbSingle.BufferLength().isSome()); 3092 const ProfileChunkedBuffer::Length bufferBytes = *cbSingle.BufferLength(); 3093 MOZ_RELEASE_ASSERT(bufferBytes >= chunkMinSize); 3094 3095 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cbSingle, 1, 1, 0, 0, 0); 3096 3097 // We will write this many blocks to fill the chunk. 3098 constexpr size_t testBlocks = 4; 3099 const ProfileChunkedBuffer::Length blockBytes = bufferBytes / testBlocks; 3100 MOZ_RELEASE_ASSERT(ULEB128Size(blockBytes) == 1, 3101 "This test assumes block sizes are small enough so that " 3102 "their ULEB128-encoded size is 1 byte"); 3103 const ProfileChunkedBuffer::Length entryBytes = 3104 blockBytes - ULEB128Size(blockBytes); 3105 3106 // First buffer-filling test: Try to write a too-big entry at the end of the 3107 // chunk. 3108 3109 // Write all but one block. 3110 for (size_t i = 0; i < testBlocks - 1; ++i) { 3111 cbSingle.Put(entryBytes, [&](Maybe<ProfileBufferEntryWriter>& aEW) { 3112 MOZ_RELEASE_ASSERT(aEW.isSome()); 3113 while (aEW->RemainingBytes() > 0) { 3114 **aEW = '0' + i; 3115 ++(*aEW); 3116 } 3117 }); 3118 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 3119 cbSingle, 1, 1 + blockBytes * (i + 1), i + 1, 0, 0); 3120 } 3121 3122 // Write the last block so that it's too big (by 1 byte) to fit in the chunk, 3123 // this should fail. 3124 const ProfileChunkedBuffer::Length remainingBytesForLastBlock = 3125 bufferBytes - blockBytes * (testBlocks - 1); 3126 MOZ_RELEASE_ASSERT(ULEB128Size(remainingBytesForLastBlock) == 1, 3127 "This test assumes block sizes are small enough so that " 3128 "their ULEB128-encoded size is 1 byte"); 3129 const ProfileChunkedBuffer::Length entryToFitRemainingBytes = 3130 remainingBytesForLastBlock - ULEB128Size(remainingBytesForLastBlock); 3131 cbSingle.Put(entryToFitRemainingBytes + 1, 3132 [&](Maybe<ProfileBufferEntryWriter>& aEW) { 3133 MOZ_RELEASE_ASSERT(aEW.isNothing()); 3134 }); 3135 // The buffer state should not have changed, apart from the failed bytes. 3136 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 3137 cbSingle, 1, 1 + blockBytes * (testBlocks - 1), testBlocks - 1, 0, 3138 remainingBytesForLastBlock + 1); 3139 3140 size_t read = 0; 3141 cbSingle.ReadEach([&](ProfileBufferEntryReader& aER) { 3142 MOZ_RELEASE_ASSERT(aER.RemainingBytes() == entryBytes); 3143 while (aER.RemainingBytes() > 0) { 3144 MOZ_RELEASE_ASSERT(*aER == '0' + read); 3145 ++aER; 3146 } 3147 ++read; 3148 }); 3149 MOZ_RELEASE_ASSERT(read == testBlocks - 1); 3150 3151 // ~Interlude~ Test AppendContent: 3152 // Create another ProfileChunkedBuffer that will use a 3153 // ProfileBufferChunkManagerWithLocalLimit, which will give away 3154 // ProfileBufferChunks that can contain 128 bytes, using up to 1KB of memory 3155 // (including usable 128 bytes and headers). 3156 constexpr size_t bufferMaxSize = 1024; 3157 ProfileBufferChunkManagerWithLocalLimit cmTarget(bufferMaxSize, chunkMinSize); 3158 ProfileChunkedBuffer cbTarget(ProfileChunkedBuffer::ThreadSafety::WithMutex, 3159 cmTarget); 3160 3161 // It should start empty. 3162 cbTarget.ReadEach( 3163 [](ProfileBufferEntryReader&) { MOZ_RELEASE_ASSERT(false); }); 3164 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cbTarget, 1, 1, 0, 0, 0); 3165 3166 // Copy the contents from cbSingle to cbTarget. 3167 cbTarget.AppendContents(cbSingle); 3168 3169 // And verify that we now have the same contents in cbTarget. 3170 read = 0; 3171 cbTarget.ReadEach([&](ProfileBufferEntryReader& aER) { 3172 MOZ_RELEASE_ASSERT(aER.RemainingBytes() == entryBytes); 3173 while (aER.RemainingBytes() > 0) { 3174 MOZ_RELEASE_ASSERT(*aER == '0' + read); 3175 ++aER; 3176 } 3177 ++read; 3178 }); 3179 MOZ_RELEASE_ASSERT(read == testBlocks - 1); 3180 // The state should be the same as the source. 3181 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 3182 cbTarget, 1, 1 + blockBytes * (testBlocks - 1), testBlocks - 1, 0, 0); 3183 3184 # ifdef DEBUG 3185 // cbSingle.Dump(); 3186 // cbTarget.Dump(); 3187 # endif 3188 3189 // Because we failed to write a too-big chunk above, the chunk was marked 3190 // full, so that entries should be consistently rejected from now on. 3191 cbSingle.Put(1, [&](Maybe<ProfileBufferEntryWriter>& aEW) { 3192 MOZ_RELEASE_ASSERT(aEW.isNothing()); 3193 }); 3194 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 3195 cbSingle, 1, 1 + blockBytes * ((testBlocks - 1)), testBlocks - 1, 0, 3196 remainingBytesForLastBlock + 1 + ULEB128Size(1u) + 1); 3197 3198 // Clear the buffer before the next test. 3199 3200 cbSingle.Clear(); 3201 // Clear() should move the index to the next chunk range -- even if it's 3202 // really reusing the same chunk. 3203 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cbSingle, 1 + bufferBytes, 3204 1 + bufferBytes, 0, 0, 0); 3205 cbSingle.ReadEach( 3206 [&](ProfileBufferEntryReader& aER) { MOZ_RELEASE_ASSERT(false); }); 3207 3208 // Second buffer-filling test: Try to write a final entry that just fits at 3209 // the end of the chunk. 3210 3211 // Write all but one block. 3212 for (size_t i = 0; i < testBlocks - 1; ++i) { 3213 cbSingle.Put(entryBytes, [&](Maybe<ProfileBufferEntryWriter>& aEW) { 3214 MOZ_RELEASE_ASSERT(aEW.isSome()); 3215 while (aEW->RemainingBytes() > 0) { 3216 **aEW = 'a' + i; 3217 ++(*aEW); 3218 } 3219 }); 3220 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 3221 cbSingle, 1 + bufferBytes, 1 + bufferBytes + blockBytes * (i + 1), 3222 i + 1, 0, 0); 3223 } 3224 3225 read = 0; 3226 cbSingle.ReadEach([&](ProfileBufferEntryReader& aER) { 3227 MOZ_RELEASE_ASSERT(aER.RemainingBytes() == entryBytes); 3228 while (aER.RemainingBytes() > 0) { 3229 MOZ_RELEASE_ASSERT(*aER == 'a' + read); 3230 ++aER; 3231 } 3232 ++read; 3233 }); 3234 MOZ_RELEASE_ASSERT(read == testBlocks - 1); 3235 3236 // Write the last block so that it fits exactly in the chunk. 3237 cbSingle.Put(entryToFitRemainingBytes, 3238 [&](Maybe<ProfileBufferEntryWriter>& aEW) { 3239 MOZ_RELEASE_ASSERT(aEW.isSome()); 3240 while (aEW->RemainingBytes() > 0) { 3241 **aEW = 'a' + (testBlocks - 1); 3242 ++(*aEW); 3243 } 3244 }); 3245 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 3246 cbSingle, 1 + bufferBytes, 1 + bufferBytes + blockBytes * testBlocks, 3247 testBlocks, 0, 0); 3248 3249 read = 0; 3250 cbSingle.ReadEach([&](ProfileBufferEntryReader& aER) { 3251 MOZ_RELEASE_ASSERT( 3252 aER.RemainingBytes() == 3253 ((read < testBlocks) ? entryBytes : entryToFitRemainingBytes)); 3254 while (aER.RemainingBytes() > 0) { 3255 MOZ_RELEASE_ASSERT(*aER == 'a' + read); 3256 ++aER; 3257 } 3258 ++read; 3259 }); 3260 MOZ_RELEASE_ASSERT(read == testBlocks); 3261 3262 // Because the single chunk has been filled, it shouldn't be possible to write 3263 // more entries. 3264 cbSingle.Put(1, [&](Maybe<ProfileBufferEntryWriter>& aEW) { 3265 MOZ_RELEASE_ASSERT(aEW.isNothing()); 3266 }); 3267 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 3268 cbSingle, 1 + bufferBytes, 1 + bufferBytes + blockBytes * testBlocks, 3269 testBlocks, 0, ULEB128Size(1u) + 1); 3270 3271 cbSingle.Clear(); 3272 // Clear() should move the index to the next chunk range -- even if it's 3273 // really reusing the same chunk. 3274 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cbSingle, 1 + bufferBytes * 2, 3275 1 + bufferBytes * 2, 0, 0, 0); 3276 cbSingle.ReadEach( 3277 [&](ProfileBufferEntryReader& aER) { MOZ_RELEASE_ASSERT(false); }); 3278 3279 // Clear() recycles the released chunk, so we should be able to record new 3280 // entries. 3281 cbSingle.Put(entryBytes, [&](Maybe<ProfileBufferEntryWriter>& aEW) { 3282 MOZ_RELEASE_ASSERT(aEW.isSome()); 3283 while (aEW->RemainingBytes() > 0) { 3284 **aEW = 'x'; 3285 ++(*aEW); 3286 } 3287 }); 3288 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( 3289 cbSingle, 1 + bufferBytes * 2, 3290 1 + bufferBytes * 2 + ULEB128Size(entryBytes) + entryBytes, 1, 0, 0); 3291 read = 0; 3292 cbSingle.ReadEach([&](ProfileBufferEntryReader& aER) { 3293 MOZ_RELEASE_ASSERT(read == 0); 3294 MOZ_RELEASE_ASSERT(aER.RemainingBytes() == entryBytes); 3295 while (aER.RemainingBytes() > 0) { 3296 MOZ_RELEASE_ASSERT(*aER == 'x'); 3297 ++aER; 3298 } 3299 ++read; 3300 }); 3301 MOZ_RELEASE_ASSERT(read == 1); 3302 3303 printf("TestChunkedBufferSingle done\n"); 3304 } 3305 3306 static void TestModuloBuffer(ModuloBuffer<>& mb, uint32_t MBSize) { 3307 using MB = ModuloBuffer<>; 3308 3309 MOZ_RELEASE_ASSERT(mb.BufferLength().Value() == MBSize); 3310 3311 // Iterator comparisons. 3312 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) == mb.ReaderAt(2)); 3313 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) != mb.ReaderAt(3)); 3314 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) < mb.ReaderAt(3)); 3315 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) <= mb.ReaderAt(2)); 3316 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) <= mb.ReaderAt(3)); 3317 MOZ_RELEASE_ASSERT(mb.ReaderAt(3) > mb.ReaderAt(2)); 3318 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) >= mb.ReaderAt(2)); 3319 MOZ_RELEASE_ASSERT(mb.ReaderAt(3) >= mb.ReaderAt(2)); 3320 3321 // Iterators indices don't wrap around (even though they may be pointing at 3322 // the same location). 3323 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) != mb.ReaderAt(MBSize + 2)); 3324 MOZ_RELEASE_ASSERT(mb.ReaderAt(MBSize + 2) != mb.ReaderAt(2)); 3325 3326 // Dereference. 3327 static_assert(std::is_same<decltype(*mb.ReaderAt(0)), const MB::Byte&>::value, 3328 "Dereferencing from a reader should return const Byte*"); 3329 static_assert(std::is_same<decltype(*mb.WriterAt(0)), MB::Byte&>::value, 3330 "Dereferencing from a writer should return Byte*"); 3331 // Contiguous between 0 and MBSize-1. 3332 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize - 1) == 3333 &*mb.ReaderAt(0) + (MBSize - 1)); 3334 // Wraps around. 3335 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize) == &*mb.ReaderAt(0)); 3336 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize + MBSize - 1) == 3337 &*mb.ReaderAt(MBSize - 1)); 3338 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize + MBSize) == &*mb.ReaderAt(0)); 3339 // Power of 2 modulo wrapping. 3340 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(uint32_t(-1)) == &*mb.ReaderAt(MBSize - 1)); 3341 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(static_cast<MB::Index>(-1)) == 3342 &*mb.ReaderAt(MBSize - 1)); 3343 3344 // Arithmetic. 3345 MB::Reader arit = mb.ReaderAt(0); 3346 MOZ_RELEASE_ASSERT(++arit == mb.ReaderAt(1)); 3347 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1)); 3348 3349 MOZ_RELEASE_ASSERT(--arit == mb.ReaderAt(0)); 3350 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0)); 3351 3352 MOZ_RELEASE_ASSERT(arit++ == mb.ReaderAt(0)); 3353 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1)); 3354 3355 MOZ_RELEASE_ASSERT(arit-- == mb.ReaderAt(1)); 3356 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0)); 3357 3358 MOZ_RELEASE_ASSERT(arit + 3 == mb.ReaderAt(3)); 3359 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0)); 3360 3361 MOZ_RELEASE_ASSERT(4 + arit == mb.ReaderAt(4)); 3362 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0)); 3363 3364 // (Can't have assignments inside asserts, hence the split.) 3365 const bool checkPlusEq = ((arit += 3) == mb.ReaderAt(3)); 3366 MOZ_RELEASE_ASSERT(checkPlusEq); 3367 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(3)); 3368 3369 MOZ_RELEASE_ASSERT((arit - 2) == mb.ReaderAt(1)); 3370 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(3)); 3371 3372 const bool checkMinusEq = ((arit -= 2) == mb.ReaderAt(1)); 3373 MOZ_RELEASE_ASSERT(checkMinusEq); 3374 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1)); 3375 3376 // Random access. 3377 MOZ_RELEASE_ASSERT(&arit[3] == &*(arit + 3)); 3378 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1)); 3379 3380 // Iterator difference. 3381 MOZ_RELEASE_ASSERT(mb.ReaderAt(3) - mb.ReaderAt(1) == 2); 3382 MOZ_RELEASE_ASSERT(mb.ReaderAt(1) - mb.ReaderAt(3) == MB::Index(-2)); 3383 3384 // Only testing Writer, as Reader is just a subset with no code differences. 3385 MB::Writer it = mb.WriterAt(0); 3386 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 0); 3387 3388 // Write two characters at the start. 3389 it.WriteObject('x'); 3390 it.WriteObject('y'); 3391 3392 // Backtrack to read them. 3393 it -= 2; 3394 // PeekObject should read without moving. 3395 MOZ_RELEASE_ASSERT(it.PeekObject<char>() == 'x'); 3396 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 0); 3397 // ReadObject should read and move past the character. 3398 MOZ_RELEASE_ASSERT(it.ReadObject<char>() == 'x'); 3399 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 1); 3400 MOZ_RELEASE_ASSERT(it.PeekObject<char>() == 'y'); 3401 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 1); 3402 MOZ_RELEASE_ASSERT(it.ReadObject<char>() == 'y'); 3403 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 2); 3404 3405 // Checking that a reader can be created from a writer. 3406 MB::Reader it2(it); 3407 MOZ_RELEASE_ASSERT(it2.CurrentIndex() == 2); 3408 // Or assigned. 3409 it2 = it; 3410 MOZ_RELEASE_ASSERT(it2.CurrentIndex() == 2); 3411 3412 // Iterator traits. 3413 static_assert(std::is_same<std::iterator_traits<MB::Reader>::difference_type, 3414 MB::Index>::value, 3415 "ModuloBuffer::Reader::difference_type should be Index"); 3416 static_assert(std::is_same<std::iterator_traits<MB::Reader>::value_type, 3417 MB::Byte>::value, 3418 "ModuloBuffer::Reader::value_type should be Byte"); 3419 static_assert(std::is_same<std::iterator_traits<MB::Reader>::pointer, 3420 const MB::Byte*>::value, 3421 "ModuloBuffer::Reader::pointer should be const Byte*"); 3422 static_assert(std::is_same<std::iterator_traits<MB::Reader>::reference, 3423 const MB::Byte&>::value, 3424 "ModuloBuffer::Reader::reference should be const Byte&"); 3425 static_assert(std::is_base_of< 3426 std::input_iterator_tag, 3427 std::iterator_traits<MB::Reader>::iterator_category>::value, 3428 "ModuloBuffer::Reader::iterator_category should be derived " 3429 "from input_iterator_tag"); 3430 static_assert(std::is_base_of< 3431 std::forward_iterator_tag, 3432 std::iterator_traits<MB::Reader>::iterator_category>::value, 3433 "ModuloBuffer::Reader::iterator_category should be derived " 3434 "from forward_iterator_tag"); 3435 static_assert(std::is_base_of< 3436 std::bidirectional_iterator_tag, 3437 std::iterator_traits<MB::Reader>::iterator_category>::value, 3438 "ModuloBuffer::Reader::iterator_category should be derived " 3439 "from bidirectional_iterator_tag"); 3440 static_assert( 3441 std::is_same<std::iterator_traits<MB::Reader>::iterator_category, 3442 std::random_access_iterator_tag>::value, 3443 "ModuloBuffer::Reader::iterator_category should be " 3444 "random_access_iterator_tag"); 3445 3446 // Use as input iterator by std::string constructor (which is only considered 3447 // with proper input iterators.) 3448 std::string s(mb.ReaderAt(0), mb.ReaderAt(2)); 3449 MOZ_RELEASE_ASSERT(s == "xy"); 3450 3451 // Write 4-byte number at index 2. 3452 it.WriteObject(int32_t(123)); 3453 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 6); 3454 // And another, which should now wrap around (but index continues on.) 3455 it.WriteObject(int32_t(456)); 3456 MOZ_RELEASE_ASSERT(it.CurrentIndex() == MBSize + 2); 3457 // Even though index==MBSize+2, we can read the object we wrote at 2. 3458 MOZ_RELEASE_ASSERT(it.ReadObject<int32_t>() == 123); 3459 MOZ_RELEASE_ASSERT(it.CurrentIndex() == MBSize + 6); 3460 // And similarly, index MBSize+6 points at the same location as index 6. 3461 MOZ_RELEASE_ASSERT(it.ReadObject<int32_t>() == 456); 3462 MOZ_RELEASE_ASSERT(it.CurrentIndex() == MBSize + MBSize + 2); 3463 } 3464 3465 void TestModuloBuffer() { 3466 printf("TestModuloBuffer...\n"); 3467 3468 // Testing ModuloBuffer with default template arguments. 3469 using MB = ModuloBuffer<>; 3470 3471 // Only 8-byte buffers, to easily test wrap-around. 3472 constexpr uint32_t MBSize = 8; 3473 3474 // MB with self-allocated heap buffer. 3475 MB mbByLength(MakePowerOfTwo32<MBSize>()); 3476 TestModuloBuffer(mbByLength, MBSize); 3477 3478 // MB taking ownership of a provided UniquePtr to a buffer. 3479 auto uniqueBuffer = MakeUnique<uint8_t[]>(MBSize); 3480 MB mbByUniquePtr(MakeUnique<uint8_t[]>(MBSize), MakePowerOfTwo32<MBSize>()); 3481 TestModuloBuffer(mbByUniquePtr, MBSize); 3482 3483 // MB using part of a buffer on the stack. The buffer is three times the 3484 // required size: The middle third is where ModuloBuffer will work, the first 3485 // and last thirds are only used to later verify that ModuloBuffer didn't go 3486 // out of its bounds. 3487 uint8_t buffer[MBSize * 3]; 3488 // Pre-fill the buffer with a known pattern, so we can later see what changed. 3489 for (size_t i = 0; i < MBSize * 3; ++i) { 3490 buffer[i] = uint8_t('A' + i); 3491 } 3492 MB mbByBuffer(&buffer[MBSize], MakePowerOfTwo32<MBSize>()); 3493 TestModuloBuffer(mbByBuffer, MBSize); 3494 3495 // Check that only the provided stack-based sub-buffer was modified. 3496 uint32_t changed = 0; 3497 for (size_t i = MBSize; i < MBSize * 2; ++i) { 3498 changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1; 3499 } 3500 // Expect at least 75% changes. 3501 MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8); 3502 3503 // Everything around the sub-buffer should be unchanged. 3504 for (size_t i = 0; i < MBSize; ++i) { 3505 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); 3506 } 3507 for (size_t i = MBSize * 2; i < MBSize * 3; ++i) { 3508 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); 3509 } 3510 3511 // Check that move-construction is allowed. This verifies that we do not 3512 // crash from a double free, when `mbByBuffer` and `mbByStolenBuffer` are both 3513 // destroyed at the end of this function. 3514 MB mbByStolenBuffer = std::move(mbByBuffer); 3515 TestModuloBuffer(mbByStolenBuffer, MBSize); 3516 3517 // Check that only the provided stack-based sub-buffer was modified. 3518 changed = 0; 3519 for (size_t i = MBSize; i < MBSize * 2; ++i) { 3520 changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1; 3521 } 3522 // Expect at least 75% changes. 3523 MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8); 3524 3525 // Everything around the sub-buffer should be unchanged. 3526 for (size_t i = 0; i < MBSize; ++i) { 3527 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); 3528 } 3529 for (size_t i = MBSize * 2; i < MBSize * 3; ++i) { 3530 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); 3531 } 3532 3533 // This test function does a `ReadInto` as directed, and checks that the 3534 // result is the same as if the copy had been done manually byte-by-byte. 3535 // `TestReadInto(3, 7, 2)` copies from index 3 to index 7, 2 bytes long. 3536 // Return the output string (from `ReadInto`) for external checks. 3537 auto TestReadInto = [](MB::Index aReadFrom, MB::Index aWriteTo, 3538 MB::Length aBytes) { 3539 constexpr uint32_t TRISize = 16; 3540 3541 // Prepare an input buffer, all different elements. 3542 uint8_t input[TRISize + 1] = "ABCDEFGHIJKLMNOP"; 3543 const MB mbInput(input, MakePowerOfTwo32<TRISize>()); 3544 3545 // Prepare an output buffer, different from input. 3546 uint8_t output[TRISize + 1] = "abcdefghijklmnop"; 3547 MB mbOutput(output, MakePowerOfTwo32<TRISize>()); 3548 3549 // Run ReadInto. 3550 auto writer = mbOutput.WriterAt(aWriteTo); 3551 mbInput.ReaderAt(aReadFrom).ReadInto(writer, aBytes); 3552 3553 // Do the same operation manually. 3554 uint8_t outputCheck[TRISize + 1] = "abcdefghijklmnop"; 3555 MB mbOutputCheck(outputCheck, MakePowerOfTwo32<TRISize>()); 3556 auto readerCheck = mbInput.ReaderAt(aReadFrom); 3557 auto writerCheck = mbOutputCheck.WriterAt(aWriteTo); 3558 for (MB::Length i = 0; i < aBytes; ++i) { 3559 *writerCheck++ = *readerCheck++; 3560 } 3561 3562 // Compare the two outputs. 3563 for (uint32_t i = 0; i < TRISize; ++i) { 3564 # ifdef TEST_MODULOBUFFER_FAILURE_DEBUG 3565 // Only used when debugging failures. 3566 if (output[i] != outputCheck[i]) { 3567 printf( 3568 "*** from=%u to=%u bytes=%u i=%u\ninput: '%s'\noutput: " 3569 "'%s'\ncheck: '%s'\n", 3570 unsigned(aReadFrom), unsigned(aWriteTo), unsigned(aBytes), 3571 unsigned(i), input, output, outputCheck); 3572 } 3573 # endif 3574 MOZ_RELEASE_ASSERT(output[i] == outputCheck[i]); 3575 } 3576 3577 # ifdef TEST_MODULOBUFFER_HELPER 3578 // Only used when adding more tests. 3579 printf("*** from=%u to=%u bytes=%u output: %s\n", unsigned(aReadFrom), 3580 unsigned(aWriteTo), unsigned(aBytes), output); 3581 # endif 3582 3583 return std::string(reinterpret_cast<const char*>(output)); 3584 }; 3585 3586 // A few manual checks: 3587 constexpr uint32_t TRISize = 16; 3588 MOZ_RELEASE_ASSERT(TestReadInto(0, 0, 0) == "abcdefghijklmnop"); 3589 MOZ_RELEASE_ASSERT(TestReadInto(0, 0, TRISize) == "ABCDEFGHIJKLMNOP"); 3590 MOZ_RELEASE_ASSERT(TestReadInto(0, 5, TRISize) == "LMNOPABCDEFGHIJK"); 3591 MOZ_RELEASE_ASSERT(TestReadInto(5, 0, TRISize) == "FGHIJKLMNOPABCDE"); 3592 3593 // Test everything! (16^3 = 4096, not too much.) 3594 for (MB::Index r = 0; r < TRISize; ++r) { 3595 for (MB::Index w = 0; w < TRISize; ++w) { 3596 for (MB::Length len = 0; len < TRISize; ++len) { 3597 TestReadInto(r, w, len); 3598 } 3599 } 3600 } 3601 3602 printf("TestModuloBuffer done\n"); 3603 } 3604 3605 void TestLiteralEmptyStringView() { 3606 printf("TestLiteralEmptyStringView...\n"); 3607 3608 static_assert(mozilla::LiteralEmptyStringView<char>() == 3609 std::string_view("")); 3610 static_assert(!!mozilla::LiteralEmptyStringView<char>().data()); 3611 static_assert(mozilla::LiteralEmptyStringView<char>().length() == 0); 3612 3613 static_assert(mozilla::LiteralEmptyStringView<char16_t>() == 3614 std::basic_string_view<char16_t>(u"")); 3615 static_assert(!!mozilla::LiteralEmptyStringView<char16_t>().data()); 3616 static_assert(mozilla::LiteralEmptyStringView<char16_t>().length() == 0); 3617 3618 printf("TestLiteralEmptyStringView done\n"); 3619 } 3620 3621 template <typename CHAR> 3622 void TestProfilerStringView() { 3623 if constexpr (std::is_same_v<CHAR, char>) { 3624 printf("TestProfilerStringView<char>...\n"); 3625 } else if constexpr (std::is_same_v<CHAR, char16_t>) { 3626 printf("TestProfilerStringView<char16_t>...\n"); 3627 } else { 3628 MOZ_RELEASE_ASSERT(false, 3629 "TestProfilerStringView only handles char and char16_t"); 3630 } 3631 3632 // Used to verify implicit constructions, as this will normally be used in 3633 // function parameters. 3634 auto BSV = [](mozilla::ProfilerStringView<CHAR>&& aBSV) { 3635 return std::move(aBSV); 3636 }; 3637 3638 // These look like string literals, as expected by some string constructors. 3639 const CHAR empty[0 + 1] = {CHAR('\0')}; 3640 const CHAR hi[2 + 1] = { 3641 CHAR('h'), 3642 CHAR('i'), 3643 CHAR('\0'), 3644 }; 3645 3646 // Literal empty string. 3647 MOZ_RELEASE_ASSERT(BSV(empty).Length() == 0); 3648 MOZ_RELEASE_ASSERT(BSV(empty).AsSpan().IsEmpty()); 3649 MOZ_RELEASE_ASSERT(BSV(empty).IsLiteral()); 3650 MOZ_RELEASE_ASSERT(!BSV(empty).IsReference()); 3651 3652 // Literal non-empty string. 3653 MOZ_RELEASE_ASSERT(BSV(hi).Length() == 2); 3654 MOZ_RELEASE_ASSERT(BSV(hi).AsSpan().Elements()); 3655 MOZ_RELEASE_ASSERT(BSV(hi).AsSpan().Elements()[0] == CHAR('h')); 3656 MOZ_RELEASE_ASSERT(BSV(hi).AsSpan().Elements()[1] == CHAR('i')); 3657 MOZ_RELEASE_ASSERT(BSV(hi).IsLiteral()); 3658 MOZ_RELEASE_ASSERT(!BSV(hi).IsReference()); 3659 3660 // std::string_view to a literal empty string. 3661 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>(empty)).Length() == 0); 3662 MOZ_RELEASE_ASSERT( 3663 BSV(std::basic_string_view<CHAR>(empty)).AsSpan().IsEmpty()); 3664 MOZ_RELEASE_ASSERT(!BSV(std::basic_string_view<CHAR>(empty)).IsLiteral()); 3665 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>(empty)).IsReference()); 3666 3667 // std::string_view to a literal non-empty string. 3668 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>(hi)).Length() == 2); 3669 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>(hi)).AsSpan().Elements()); 3670 MOZ_RELEASE_ASSERT( 3671 BSV(std::basic_string_view<CHAR>(hi)).AsSpan().Elements()[0] == 3672 CHAR('h')); 3673 MOZ_RELEASE_ASSERT( 3674 BSV(std::basic_string_view<CHAR>(hi)).AsSpan().Elements()[1] == 3675 CHAR('i')); 3676 MOZ_RELEASE_ASSERT(!BSV(std::basic_string_view<CHAR>(hi)).IsLiteral()); 3677 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>(hi)).IsReference()); 3678 3679 // Default std::string_view points at nullptr, ProfilerStringView converts it 3680 // to the literal empty string. 3681 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>()).Length() == 0); 3682 MOZ_RELEASE_ASSERT(!std::basic_string_view<CHAR>().data()); 3683 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>()).AsSpan().IsEmpty()); 3684 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>()).IsLiteral()); 3685 MOZ_RELEASE_ASSERT(!BSV(std::basic_string_view<CHAR>()).IsReference()); 3686 3687 // std::string to a literal empty string. 3688 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(empty)).Length() == 0); 3689 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(empty)).AsSpan().IsEmpty()); 3690 MOZ_RELEASE_ASSERT(!BSV(std::basic_string<CHAR>(empty)).IsLiteral()); 3691 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(empty)).IsReference()); 3692 3693 // std::string to a literal non-empty string. 3694 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(hi)).Length() == 2); 3695 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(hi)).AsSpan().Elements()); 3696 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(hi)).AsSpan().Elements()[0] == 3697 CHAR('h')); 3698 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(hi)).AsSpan().Elements()[1] == 3699 CHAR('i')); 3700 MOZ_RELEASE_ASSERT(!BSV(std::basic_string<CHAR>(hi)).IsLiteral()); 3701 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(hi)).IsReference()); 3702 3703 // Default std::string contains an empty null-terminated string. 3704 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>()).Length() == 0); 3705 MOZ_RELEASE_ASSERT(std::basic_string<CHAR>().data()); 3706 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>()).AsSpan().IsEmpty()); 3707 MOZ_RELEASE_ASSERT(!BSV(std::basic_string<CHAR>()).IsLiteral()); 3708 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>()).IsReference()); 3709 3710 // Class that quacks like nsTString (with Data(), Length(), IsLiteral()), to 3711 // check that ProfilerStringView can read from them. 3712 class FakeNsTString { 3713 public: 3714 FakeNsTString(const CHAR* aData, size_t aLength, bool aIsLiteral) 3715 : mData(aData), mLength(aLength), mIsLiteral(aIsLiteral) {} 3716 3717 const CHAR* Data() const { return mData; } 3718 size_t Length() const { return mLength; } 3719 bool IsLiteral() const { return mIsLiteral; } 3720 3721 private: 3722 const CHAR* mData; 3723 size_t mLength; 3724 bool mIsLiteral; 3725 }; 3726 3727 // FakeNsTString to nullptr. 3728 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(nullptr, 0, true)).Length() == 0); 3729 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(nullptr, 0, true)).AsSpan().IsEmpty()); 3730 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(nullptr, 0, true)).IsLiteral()); 3731 MOZ_RELEASE_ASSERT(!BSV(FakeNsTString(nullptr, 0, true)).IsReference()); 3732 3733 // FakeNsTString to a literal empty string. 3734 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(empty, 0, true)).Length() == 0); 3735 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(empty, 0, true)).AsSpan().IsEmpty()); 3736 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(empty, 0, true)).IsLiteral()); 3737 MOZ_RELEASE_ASSERT(!BSV(FakeNsTString(empty, 0, true)).IsReference()); 3738 3739 // FakeNsTString to a literal non-empty string. 3740 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, true)).Length() == 2); 3741 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, true)).AsSpan().Elements()); 3742 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, true)).AsSpan().Elements()[0] == 3743 CHAR('h')); 3744 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, true)).AsSpan().Elements()[1] == 3745 CHAR('i')); 3746 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, true)).IsLiteral()); 3747 MOZ_RELEASE_ASSERT(!BSV(FakeNsTString(hi, 2, true)).IsReference()); 3748 3749 // FakeNsTString to a non-literal non-empty string. 3750 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, false)).Length() == 2); 3751 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, false)).AsSpan().Elements()); 3752 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, false)).AsSpan().Elements()[0] == 3753 CHAR('h')); 3754 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, false)).AsSpan().Elements()[1] == 3755 CHAR('i')); 3756 MOZ_RELEASE_ASSERT(!BSV(FakeNsTString(hi, 2, false)).IsLiteral()); 3757 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, false)).IsReference()); 3758 3759 // Serialization and deserialization (with ownership). 3760 constexpr size_t bufferMaxSize = 1024; 3761 constexpr ProfileChunkedBuffer::Length chunkMinSize = 128; 3762 ProfileBufferChunkManagerWithLocalLimit cm(bufferMaxSize, chunkMinSize); 3763 ProfileChunkedBuffer cb(ProfileChunkedBuffer::ThreadSafety::WithMutex, cm); 3764 3765 // Literal string, serialized as raw pointer. 3766 MOZ_RELEASE_ASSERT(cb.PutObject(BSV(hi))); 3767 { 3768 unsigned read = 0; 3769 ProfilerStringView<CHAR> outerBSV; 3770 cb.ReadEach([&](ProfileBufferEntryReader& aER) { 3771 ++read; 3772 auto bsv = aER.ReadObject<ProfilerStringView<CHAR>>(); 3773 MOZ_RELEASE_ASSERT(bsv.Length() == 2); 3774 MOZ_RELEASE_ASSERT(bsv.AsSpan().Elements()); 3775 MOZ_RELEASE_ASSERT(bsv.AsSpan().Elements()[0] == CHAR('h')); 3776 MOZ_RELEASE_ASSERT(bsv.AsSpan().Elements()[1] == CHAR('i')); 3777 MOZ_RELEASE_ASSERT(bsv.IsLiteral()); 3778 MOZ_RELEASE_ASSERT(!bsv.IsReference()); 3779 outerBSV = std::move(bsv); 3780 }); 3781 MOZ_RELEASE_ASSERT(read == 1); 3782 MOZ_RELEASE_ASSERT(outerBSV.Length() == 2); 3783 MOZ_RELEASE_ASSERT(outerBSV.AsSpan().Elements()); 3784 MOZ_RELEASE_ASSERT(outerBSV.AsSpan().Elements()[0] == CHAR('h')); 3785 MOZ_RELEASE_ASSERT(outerBSV.AsSpan().Elements()[1] == CHAR('i')); 3786 MOZ_RELEASE_ASSERT(outerBSV.IsLiteral()); 3787 MOZ_RELEASE_ASSERT(!outerBSV.IsReference()); 3788 } 3789 3790 MOZ_RELEASE_ASSERT(cb.GetState().mRangeStart == 1u); 3791 3792 cb.Clear(); 3793 3794 // Non-literal string, content is serialized. 3795 3796 // We'll try to write 4 strings, such that the 4th one will cross into the 3797 // next chunk. 3798 unsigned guessedChunkBytes = unsigned(cb.GetState().mRangeStart) - 1u; 3799 static constexpr unsigned stringCount = 4u; 3800 const unsigned stringSize = 3801 guessedChunkBytes / stringCount / sizeof(CHAR) + 3u; 3802 3803 std::basic_string<CHAR> longString; 3804 longString.reserve(stringSize); 3805 for (unsigned i = 0; i < stringSize; ++i) { 3806 longString += CHAR('0' + i); 3807 } 3808 3809 for (unsigned i = 0; i < stringCount; ++i) { 3810 MOZ_RELEASE_ASSERT(cb.PutObject(BSV(longString))); 3811 } 3812 3813 { 3814 unsigned read = 0; 3815 ProfilerStringView<CHAR> outerBSV; 3816 cb.ReadEach([&](ProfileBufferEntryReader& aER) { 3817 ++read; 3818 { 3819 auto bsv = aER.ReadObject<ProfilerStringView<CHAR>>(); 3820 MOZ_RELEASE_ASSERT(bsv.Length() == stringSize); 3821 MOZ_RELEASE_ASSERT(bsv.AsSpan().Elements()); 3822 for (unsigned i = 0; i < stringSize; ++i) { 3823 MOZ_RELEASE_ASSERT(bsv.AsSpan().Elements()[i] == CHAR('0' + i)); 3824 longString += '0' + i; 3825 } 3826 MOZ_RELEASE_ASSERT(!bsv.IsLiteral()); 3827 // The first 3 should be references (because they fit in one chunk, so 3828 // they can be referenced directly), which the 4th one have to be copied 3829 // out of two chunks and stitched back together. 3830 MOZ_RELEASE_ASSERT(bsv.IsReference() == (read != 4)); 3831 3832 // Test move of ownership. 3833 outerBSV = std::move(bsv); 3834 // After a move, references stay complete, while a non-reference had a 3835 // buffer that has been moved out. 3836 // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move) 3837 MOZ_RELEASE_ASSERT(bsv.Length() == ((read != 4) ? stringSize : 0)); 3838 } 3839 3840 MOZ_RELEASE_ASSERT(outerBSV.Length() == stringSize); 3841 MOZ_RELEASE_ASSERT(outerBSV.AsSpan().Elements()); 3842 for (unsigned i = 0; i < stringSize; ++i) { 3843 MOZ_RELEASE_ASSERT(outerBSV.AsSpan().Elements()[i] == CHAR('0' + i)); 3844 longString += '0' + i; 3845 } 3846 MOZ_RELEASE_ASSERT(!outerBSV.IsLiteral()); 3847 MOZ_RELEASE_ASSERT(outerBSV.IsReference() == (read != 4)); 3848 }); 3849 MOZ_RELEASE_ASSERT(read == 4); 3850 } 3851 3852 if constexpr (std::is_same_v<CHAR, char>) { 3853 printf("TestProfilerStringView<char> done\n"); 3854 } else if constexpr (std::is_same_v<CHAR, char16_t>) { 3855 printf("TestProfilerStringView<char16_t> done\n"); 3856 } 3857 } 3858 3859 void TestProfilerDependencies() { 3860 TestPowerOfTwoMask(); 3861 TestPowerOfTwo(); 3862 TestLEB128(); 3863 TestJSONTimeOutput(); 3864 TestChunk(); 3865 TestChunkManagerSingle(); 3866 TestChunkManagerWithLocalLimit(); 3867 TestControlledChunkManagerUpdate(); 3868 TestControlledChunkManagerWithLocalLimit(); 3869 TestChunkedBuffer(); 3870 TestChunkedBufferSingle(); 3871 TestModuloBuffer(); 3872 TestLiteralEmptyStringView(); 3873 TestProfilerStringView<char>(); 3874 TestProfilerStringView<char16_t>(); 3875 } 3876 3877 // Increase the depth, to a maximum (to avoid too-deep recursion). 3878 static constexpr size_t NextDepth(size_t aDepth) { 3879 constexpr size_t MAX_DEPTH = 128; 3880 return (aDepth < MAX_DEPTH) ? (aDepth + 1) : aDepth; 3881 } 3882 3883 Atomic<bool, Relaxed> sStopFibonacci; 3884 3885 // Compute fibonacci the hard way (recursively: `f(n)=f(n-1)+f(n-2)`), and 3886 // prevent inlining. 3887 // The template parameter makes each depth be a separate function, to better 3888 // distinguish them in the profiler output. 3889 template <size_t DEPTH = 0> 3890 MOZ_NEVER_INLINE unsigned long long Fibonacci(unsigned long long n) { 3891 AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING("fib", OTHER, std::to_string(DEPTH)); 3892 if (n == 0) { 3893 return 0; 3894 } 3895 if (n == 1) { 3896 return 1; 3897 } 3898 if (DEPTH < 5 && sStopFibonacci) { 3899 return 1'000'000'000; 3900 } 3901 TimeStamp start = TimeStamp::Now(); 3902 static constexpr size_t MAX_MARKER_DEPTH = 10; 3903 unsigned long long f2 = Fibonacci<NextDepth(DEPTH)>(n - 2); 3904 if (DEPTH == 0) { 3905 BASE_PROFILER_MARKER_UNTYPED("Half-way through Fibonacci", OTHER); 3906 } 3907 unsigned long long f1 = Fibonacci<NextDepth(DEPTH)>(n - 1); 3908 if (DEPTH < MAX_MARKER_DEPTH) { 3909 BASE_PROFILER_MARKER_TEXT("fib", OTHER, 3910 MarkerTiming::IntervalUntilNowFrom(start), 3911 std::to_string(DEPTH)); 3912 } 3913 return f2 + f1; 3914 } 3915 3916 void TestProfiler() { 3917 printf("TestProfiler starting -- pid: %" PRIu64 ", tid: %" PRIu64 "\n", 3918 uint64_t(baseprofiler::profiler_current_process_id().ToNumber()), 3919 uint64_t(baseprofiler::profiler_current_thread_id().ToNumber())); 3920 // ::SleepMilli(10000); 3921 3922 TestProfilerDependencies(); 3923 3924 { 3925 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_is_active()); 3926 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_being_profiled()); 3927 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_sleeping()); 3928 3929 const baseprofiler::BaseProfilerThreadId mainThreadId = 3930 mozilla::baseprofiler::profiler_current_thread_id(); 3931 3932 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::profiler_main_thread_id() == 3933 mainThreadId); 3934 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::profiler_is_main_thread()); 3935 3936 std::thread testThread([&]() { 3937 const baseprofiler::BaseProfilerThreadId testThreadId = 3938 mozilla::baseprofiler::profiler_current_thread_id(); 3939 MOZ_RELEASE_ASSERT(testThreadId != mainThreadId); 3940 3941 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::profiler_main_thread_id() != 3942 testThreadId); 3943 MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_is_main_thread()); 3944 }); 3945 testThread.join(); 3946 3947 printf("profiler_start()...\n"); 3948 Vector<const char*> filters; 3949 // Profile all registered threads. 3950 MOZ_RELEASE_ASSERT(filters.append("")); 3951 const uint32_t features = baseprofiler::ProfilerFeature::StackWalk; 3952 baseprofiler::profiler_start(baseprofiler::BASE_PROFILER_DEFAULT_ENTRIES, 3953 BASE_PROFILER_DEFAULT_INTERVAL, features, 3954 filters.begin(), filters.length()); 3955 3956 MOZ_RELEASE_ASSERT(baseprofiler::profiler_is_active()); 3957 MOZ_RELEASE_ASSERT(baseprofiler::profiler_thread_is_being_profiled()); 3958 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_sleeping()); 3959 3960 sStopFibonacci = false; 3961 3962 std::thread threadFib([]() { 3963 AUTO_BASE_PROFILER_REGISTER_THREAD("fibonacci"); 3964 SleepMilli(5); 3965 auto cause = baseprofiler::profiler_capture_backtrace(); 3966 AUTO_BASE_PROFILER_MARKER_TEXT( 3967 "fibonacci", OTHER, MarkerStack::TakeBacktrace(std::move(cause)), 3968 "First leaf call"); 3969 static const unsigned long long fibStart = 37; 3970 printf("Fibonacci(%llu)...\n", fibStart); 3971 AUTO_BASE_PROFILER_LABEL("Label around Fibonacci", OTHER); 3972 3973 unsigned long long f = Fibonacci(fibStart); 3974 printf("Fibonacci(%llu) = %llu\n", fibStart, f); 3975 }); 3976 3977 std::thread threadCancelFib([]() { 3978 AUTO_BASE_PROFILER_REGISTER_THREAD("fibonacci canceller"); 3979 SleepMilli(5); 3980 AUTO_BASE_PROFILER_MARKER_TEXT("fibonacci", OTHER, {}, "Canceller"); 3981 static const int waitMaxSeconds = 10; 3982 for (int i = 0; i < waitMaxSeconds; ++i) { 3983 if (sStopFibonacci) { 3984 AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING("fibCancel", OTHER, 3985 std::to_string(i)); 3986 return; 3987 } 3988 AUTO_BASE_PROFILER_THREAD_SLEEP; 3989 SleepMilli(1000); 3990 } 3991 AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING("fibCancel", OTHER, 3992 "Cancelling!"); 3993 sStopFibonacci = true; 3994 }); 3995 3996 { 3997 AUTO_BASE_PROFILER_MARKER_TEXT("main thread", OTHER, {}, 3998 "joining fibonacci thread"); 3999 AUTO_BASE_PROFILER_THREAD_SLEEP; 4000 threadFib.join(); 4001 } 4002 4003 { 4004 AUTO_BASE_PROFILER_MARKER_TEXT("main thread", OTHER, {}, 4005 "joining fibonacci-canceller thread"); 4006 sStopFibonacci = true; 4007 AUTO_BASE_PROFILER_THREAD_SLEEP; 4008 threadCancelFib.join(); 4009 } 4010 4011 // Just making sure all payloads know how to (de)serialize and stream. 4012 4013 MOZ_RELEASE_ASSERT( 4014 baseprofiler::AddMarker("markers 2.0 without options (omitted)", 4015 mozilla::baseprofiler::category::OTHER)); 4016 4017 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4018 "markers 2.0 without options (implicit brace-init)", 4019 mozilla::baseprofiler::category::OTHER, {})); 4020 4021 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4022 "markers 2.0 without options (explicit init)", 4023 mozilla::baseprofiler::category::OTHER, MarkerOptions())); 4024 4025 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4026 "markers 2.0 without options (explicit brace-init)", 4027 mozilla::baseprofiler::category::OTHER, MarkerOptions{})); 4028 4029 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4030 "markers 2.0 with one option (implicit)", 4031 mozilla::baseprofiler::category::OTHER, MarkerInnerWindowId(123))); 4032 4033 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4034 "markers 2.0 with one option (implicit brace-init)", 4035 mozilla::baseprofiler::category::OTHER, {MarkerInnerWindowId(123)})); 4036 4037 MOZ_RELEASE_ASSERT( 4038 baseprofiler::AddMarker("markers 2.0 with one option (explicit init)", 4039 mozilla::baseprofiler::category::OTHER, 4040 MarkerOptions(MarkerInnerWindowId(123)))); 4041 4042 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4043 "markers 2.0 with one option (explicit brace-init)", 4044 mozilla::baseprofiler::category::OTHER, 4045 MarkerOptions{MarkerInnerWindowId(123)})); 4046 4047 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4048 "markers 2.0 with two options (implicit brace-init)", 4049 mozilla::baseprofiler::category::OTHER, 4050 {MarkerInnerWindowId(123), MarkerStack::Capture()})); 4051 4052 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4053 "markers 2.0 with two options (explicit init)", 4054 mozilla::baseprofiler::category::OTHER, 4055 MarkerOptions(MarkerInnerWindowId(123), MarkerStack::Capture()))); 4056 4057 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4058 "markers 2.0 with two options (explicit brace-init)", 4059 mozilla::baseprofiler::category::OTHER, 4060 MarkerOptions{MarkerInnerWindowId(123), MarkerStack::Capture()})); 4061 4062 MOZ_RELEASE_ASSERT( 4063 baseprofiler::AddMarker("default-templated markers 2.0 without options", 4064 mozilla::baseprofiler::category::OTHER)); 4065 4066 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4067 "default-templated markers 2.0 with option", 4068 mozilla::baseprofiler::category::OTHER, MarkerInnerWindowId(123))); 4069 4070 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4071 "explicitly-default-templated markers 2.0 without options", 4072 mozilla::baseprofiler::category::OTHER, {}, 4073 ::mozilla::baseprofiler::markers::NoPayload{})); 4074 4075 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4076 "explicitly-default-templated markers 2.0 with option", 4077 mozilla::baseprofiler::category::OTHER, MarkerInnerWindowId(123), 4078 ::mozilla::baseprofiler::markers::NoPayload{})); 4079 4080 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4081 "stackmarker", mozilla::baseprofiler::category::OTHER, {}, 4082 mozilla::baseprofiler::markers::StackMarker{})); 4083 4084 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4085 "text", mozilla::baseprofiler::category::OTHER, {}, 4086 mozilla::baseprofiler::markers::TextMarker{}, "text text")); 4087 4088 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4089 "media sample", mozilla::baseprofiler::category::OTHER, {}, 4090 mozilla::baseprofiler::markers::MediaSampleMarker{}, 123, 456, 789)); 4091 4092 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4093 "video falling behind", mozilla::baseprofiler::category::OTHER, {}, 4094 mozilla::baseprofiler::markers::VideoFallingBehindMarker{}, 123, 456)); 4095 4096 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker( 4097 "video sink render", mozilla::baseprofiler::category::OTHER, {}, 4098 mozilla::baseprofiler::markers::VideoSinkRenderMarker{}, 123)); 4099 4100 printf("Sleep 1s...\n"); 4101 { 4102 AUTO_BASE_PROFILER_THREAD_SLEEP; 4103 SleepMilli(1000); 4104 } 4105 4106 printf("baseprofiler_pause()...\n"); 4107 baseprofiler::profiler_pause(); 4108 4109 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_being_profiled()); 4110 4111 Maybe<baseprofiler::ProfilerBufferInfo> info = 4112 baseprofiler::profiler_get_buffer_info(); 4113 MOZ_RELEASE_ASSERT(info.isSome()); 4114 printf("Profiler buffer range: %llu .. %llu (%llu bytes)\n", 4115 static_cast<unsigned long long>(info->mRangeStart), 4116 static_cast<unsigned long long>(info->mRangeEnd), 4117 // sizeof(ProfileBufferEntry) == 9 4118 (static_cast<unsigned long long>(info->mRangeEnd) - 4119 static_cast<unsigned long long>(info->mRangeStart)) * 4120 9); 4121 printf("Stats: min(us) .. mean(us) .. max(us) [count]\n"); 4122 printf("- Intervals: %7.1f .. %7.1f .. %7.1f [%u]\n", 4123 info->mIntervalsUs.min, 4124 info->mIntervalsUs.sum / info->mIntervalsUs.n, 4125 info->mIntervalsUs.max, info->mIntervalsUs.n); 4126 printf("- Overheads: %7.1f .. %7.1f .. %7.1f [%u]\n", 4127 info->mOverheadsUs.min, 4128 info->mOverheadsUs.sum / info->mOverheadsUs.n, 4129 info->mOverheadsUs.max, info->mOverheadsUs.n); 4130 printf(" - Locking: %7.1f .. %7.1f .. %7.1f [%u]\n", 4131 info->mLockingsUs.min, info->mLockingsUs.sum / info->mLockingsUs.n, 4132 info->mLockingsUs.max, info->mLockingsUs.n); 4133 printf(" - Clearning: %7.1f .. %7.1f .. %7.1f [%u]\n", 4134 info->mCleaningsUs.min, 4135 info->mCleaningsUs.sum / info->mCleaningsUs.n, 4136 info->mCleaningsUs.max, info->mCleaningsUs.n); 4137 printf(" - Counters: %7.1f .. %7.1f .. %7.1f [%u]\n", 4138 info->mCountersUs.min, info->mCountersUs.sum / info->mCountersUs.n, 4139 info->mCountersUs.max, info->mCountersUs.n); 4140 printf(" - Threads: %7.1f .. %7.1f .. %7.1f [%u]\n", 4141 info->mThreadsUs.min, info->mThreadsUs.sum / info->mThreadsUs.n, 4142 info->mThreadsUs.max, info->mThreadsUs.n); 4143 4144 printf("baseprofiler_get_profile()...\n"); 4145 UniquePtr<char[]> profile = baseprofiler::profiler_get_profile(); 4146 4147 // Use a string view over the profile contents, for easier testing. 4148 std::string_view profileSV = profile.get(); 4149 4150 constexpr const auto svnpos = std::string_view::npos; 4151 // TODO: Properly parse profile and check fields. 4152 // Check for some expected marker schema JSON output. 4153 MOZ_RELEASE_ASSERT(profileSV.find("\"markerSchema\":[") != svnpos); 4154 MOZ_RELEASE_ASSERT(profileSV.find("\"name\":\"Text\",") != svnpos); 4155 MOZ_RELEASE_ASSERT(profileSV.find("\"name\":\"StackMarker\",") != svnpos); 4156 MOZ_RELEASE_ASSERT(profileSV.find("\"name\":\"MediaSample\",") != svnpos); 4157 MOZ_RELEASE_ASSERT(profileSV.find("\"display\":[") != svnpos); 4158 MOZ_RELEASE_ASSERT(profileSV.find("\"marker-chart\"") != svnpos); 4159 MOZ_RELEASE_ASSERT(profileSV.find("\"marker-table\"") != svnpos); 4160 MOZ_RELEASE_ASSERT(profileSV.find("\"format\":\"string\"") != svnpos); 4161 // TODO: Add more checks for what's expected in the profile. Some of them 4162 // are done in gtest's. 4163 4164 printf("baseprofiler_save_profile_to_file()...\n"); 4165 baseprofiler::baseprofiler_save_profile_to_file( 4166 "TestProfiler_profile.json"); 4167 4168 printf("profiler_stop()...\n"); 4169 baseprofiler::profiler_stop(); 4170 4171 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_is_active()); 4172 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_being_profiled()); 4173 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_sleeping()); 4174 4175 printf("profiler_shutdown()...\n"); 4176 } 4177 4178 printf("TestProfiler done\n"); 4179 } 4180 4181 // Minimal string escaping, similar to how C++ stringliterals should be entered, 4182 // to help update comparison strings in tests below. 4183 void printEscaped(std::string_view aString) { 4184 for (const char c : aString) { 4185 switch (c) { 4186 case '\n': 4187 fprintf(stderr, "\\n\n"); 4188 break; 4189 case '"': 4190 fprintf(stderr, "\\\""); 4191 break; 4192 case '\\': 4193 fprintf(stderr, "\\\\"); 4194 break; 4195 default: 4196 if (c >= ' ' && c <= '~') { 4197 fprintf(stderr, "%c", c); 4198 } else { 4199 fprintf(stderr, "\\x%02x", unsigned(c)); 4200 } 4201 break; 4202 } 4203 } 4204 } 4205 4206 // Run aF(SpliceableChunkedJSONWriter&, UniqueJSONStrings&) from inside a JSON 4207 // array, then output the string table, and compare the full output to 4208 // aExpected. 4209 template <typename F> 4210 static void VerifyUniqueStringContents( 4211 F&& aF, std::string_view aExpectedData, 4212 std::string_view aExpectedUniqueStrings, 4213 mozilla::baseprofiler::UniqueJSONStrings* aUniqueStringsOrNull = nullptr) { 4214 mozilla::baseprofiler::SpliceableChunkedJSONWriter writer{ 4215 FailureLatchInfallibleSource::Singleton()}; 4216 4217 MOZ_RELEASE_ASSERT(!writer.ChunkedWriteFunc().Fallible()); 4218 MOZ_RELEASE_ASSERT(!writer.ChunkedWriteFunc().Failed()); 4219 MOZ_RELEASE_ASSERT(!writer.ChunkedWriteFunc().GetFailure()); 4220 MOZ_RELEASE_ASSERT(&writer.ChunkedWriteFunc().SourceFailureLatch() == 4221 &mozilla::FailureLatchInfallibleSource::Singleton()); 4222 MOZ_RELEASE_ASSERT( 4223 &std::as_const(writer.ChunkedWriteFunc()).SourceFailureLatch() == 4224 &mozilla::FailureLatchInfallibleSource::Singleton()); 4225 4226 MOZ_RELEASE_ASSERT(!writer.Fallible()); 4227 MOZ_RELEASE_ASSERT(!writer.Failed()); 4228 MOZ_RELEASE_ASSERT(!writer.GetFailure()); 4229 MOZ_RELEASE_ASSERT(&writer.SourceFailureLatch() == 4230 &mozilla::FailureLatchInfallibleSource::Singleton()); 4231 MOZ_RELEASE_ASSERT(&std::as_const(writer).SourceFailureLatch() == 4232 &mozilla::FailureLatchInfallibleSource::Singleton()); 4233 4234 // By default use a local UniqueJSONStrings, otherwise use the one provided. 4235 mozilla::baseprofiler::UniqueJSONStrings localUniqueStrings{ 4236 FailureLatchInfallibleSource::Singleton()}; 4237 MOZ_RELEASE_ASSERT(!localUniqueStrings.Fallible()); 4238 MOZ_RELEASE_ASSERT(!localUniqueStrings.Failed()); 4239 MOZ_RELEASE_ASSERT(!localUniqueStrings.GetFailure()); 4240 MOZ_RELEASE_ASSERT(&localUniqueStrings.SourceFailureLatch() == 4241 &mozilla::FailureLatchInfallibleSource::Singleton()); 4242 MOZ_RELEASE_ASSERT(&std::as_const(localUniqueStrings).SourceFailureLatch() == 4243 &mozilla::FailureLatchInfallibleSource::Singleton()); 4244 4245 mozilla::baseprofiler::UniqueJSONStrings& uniqueStrings = 4246 aUniqueStringsOrNull ? *aUniqueStringsOrNull : localUniqueStrings; 4247 MOZ_RELEASE_ASSERT(!uniqueStrings.Failed()); 4248 MOZ_RELEASE_ASSERT(!uniqueStrings.GetFailure()); 4249 4250 writer.Start(); 4251 { 4252 writer.StartArrayProperty("data"); 4253 { 4254 std::forward<F>(aF)(writer, uniqueStrings); 4255 } 4256 writer.EndArray(); 4257 4258 writer.StartArrayProperty("stringTable"); 4259 { 4260 uniqueStrings.SpliceStringTableElements(writer); 4261 } 4262 writer.EndArray(); 4263 } 4264 writer.End(); 4265 4266 MOZ_RELEASE_ASSERT(!uniqueStrings.Failed()); 4267 MOZ_RELEASE_ASSERT(!uniqueStrings.GetFailure()); 4268 4269 MOZ_RELEASE_ASSERT(!writer.ChunkedWriteFunc().Failed()); 4270 MOZ_RELEASE_ASSERT(!writer.ChunkedWriteFunc().GetFailure()); 4271 4272 MOZ_RELEASE_ASSERT(!writer.Failed()); 4273 MOZ_RELEASE_ASSERT(!writer.GetFailure()); 4274 4275 UniquePtr<char[]> jsonString = writer.ChunkedWriteFunc().CopyData(); 4276 MOZ_RELEASE_ASSERT(jsonString); 4277 std::string_view jsonStringView(jsonString.get()); 4278 const size_t length = writer.ChunkedWriteFunc().Length(); 4279 MOZ_RELEASE_ASSERT(length == jsonStringView.length()); 4280 std::string expected = "{\"data\":["; 4281 expected += aExpectedData; 4282 expected += "],\"stringTable\":["; 4283 expected += aExpectedUniqueStrings; 4284 expected += "]}"; 4285 if (jsonStringView != expected) { 4286 fprintf(stderr, 4287 "Expected:\n" 4288 "------\n"); 4289 printEscaped(expected); 4290 fprintf(stderr, 4291 "\n" 4292 "------\n" 4293 "Actual:\n" 4294 "------\n"); 4295 printEscaped(jsonStringView); 4296 fprintf(stderr, 4297 "\n" 4298 "------\n"); 4299 } 4300 MOZ_RELEASE_ASSERT(jsonStringView == expected); 4301 } 4302 4303 void TestUniqueJSONStrings() { 4304 printf("TestUniqueJSONStrings...\n"); 4305 4306 using SCJW = mozilla::baseprofiler::SpliceableChunkedJSONWriter; 4307 using UJS = mozilla::baseprofiler::UniqueJSONStrings; 4308 4309 // Empty everything. 4310 VerifyUniqueStringContents([](SCJW& aWriter, UJS& aUniqueStrings) {}, "", ""); 4311 4312 // Empty unique strings. 4313 VerifyUniqueStringContents( 4314 [](SCJW& aWriter, UJS& aUniqueStrings) { 4315 aWriter.StringElement("string"); 4316 }, 4317 R"("string")", ""); 4318 4319 // One unique string. 4320 VerifyUniqueStringContents( 4321 [](SCJW& aWriter, UJS& aUniqueStrings) { 4322 aUniqueStrings.WriteElement(aWriter, "string"); 4323 }, 4324 "0", R"("string")"); 4325 4326 // One unique string twice. 4327 VerifyUniqueStringContents( 4328 [](SCJW& aWriter, UJS& aUniqueStrings) { 4329 aUniqueStrings.WriteElement(aWriter, "string"); 4330 aUniqueStrings.WriteElement(aWriter, "string"); 4331 }, 4332 "0,0", R"("string")"); 4333 4334 // Two single unique strings. 4335 VerifyUniqueStringContents( 4336 [](SCJW& aWriter, UJS& aUniqueStrings) { 4337 aUniqueStrings.WriteElement(aWriter, "string0"); 4338 aUniqueStrings.WriteElement(aWriter, "string1"); 4339 }, 4340 "0,1", R"("string0","string1")"); 4341 4342 // Two unique strings with repetition. 4343 VerifyUniqueStringContents( 4344 [](SCJW& aWriter, UJS& aUniqueStrings) { 4345 aUniqueStrings.WriteElement(aWriter, "string0"); 4346 aUniqueStrings.WriteElement(aWriter, "string1"); 4347 aUniqueStrings.WriteElement(aWriter, "string0"); 4348 }, 4349 "0,1,0", R"("string0","string1")"); 4350 4351 // Mix some object properties, for coverage. 4352 VerifyUniqueStringContents( 4353 [](SCJW& aWriter, UJS& aUniqueStrings) { 4354 aUniqueStrings.WriteElement(aWriter, "string0"); 4355 aWriter.StartObjectElement(); 4356 { 4357 aUniqueStrings.WriteProperty(aWriter, "p0", "prop"); 4358 aUniqueStrings.WriteProperty(aWriter, "p1", "string0"); 4359 aUniqueStrings.WriteProperty(aWriter, "p2", "prop"); 4360 } 4361 aWriter.EndObject(); 4362 aUniqueStrings.WriteElement(aWriter, "string1"); 4363 aUniqueStrings.WriteElement(aWriter, "string0"); 4364 aUniqueStrings.WriteElement(aWriter, "prop"); 4365 }, 4366 R"(0,{"p0":1,"p1":0,"p2":1},2,0,1)", R"("string0","prop","string1")"); 4367 4368 // Unique string table with pre-existing data. 4369 { 4370 UJS ujs{FailureLatchInfallibleSource::Singleton()}; 4371 { 4372 SCJW writer{FailureLatchInfallibleSource::Singleton()}; 4373 ujs.WriteElement(writer, "external0"); 4374 ujs.WriteElement(writer, "external1"); 4375 ujs.WriteElement(writer, "external0"); 4376 } 4377 VerifyUniqueStringContents( 4378 [](SCJW& aWriter, UJS& aUniqueStrings) { 4379 aUniqueStrings.WriteElement(aWriter, "string0"); 4380 aUniqueStrings.WriteElement(aWriter, "string1"); 4381 aUniqueStrings.WriteElement(aWriter, "string0"); 4382 }, 4383 "2,3,2", R"("external0","external1","string0","string1")", &ujs); 4384 } 4385 4386 // Unique string table with pre-existing data from another table. 4387 { 4388 UJS ujs{FailureLatchInfallibleSource::Singleton()}; 4389 { 4390 SCJW writer{FailureLatchInfallibleSource::Singleton()}; 4391 ujs.WriteElement(writer, "external0"); 4392 ujs.WriteElement(writer, "external1"); 4393 ujs.WriteElement(writer, "external0"); 4394 } 4395 UJS ujsCopy(FailureLatchInfallibleSource::Singleton(), ujs, 4396 mozilla::ProgressLogger{}); 4397 VerifyUniqueStringContents( 4398 [](SCJW& aWriter, UJS& aUniqueStrings) { 4399 aUniqueStrings.WriteElement(aWriter, "string0"); 4400 aUniqueStrings.WriteElement(aWriter, "string1"); 4401 aUniqueStrings.WriteElement(aWriter, "string0"); 4402 }, 4403 "2,3,2", R"("external0","external1","string0","string1")", &ujs); 4404 } 4405 4406 // Unique string table through SpliceableJSONWriter. 4407 VerifyUniqueStringContents( 4408 [](SCJW& aWriter, UJS& aUniqueStrings) { 4409 aWriter.SetUniqueStrings(aUniqueStrings); 4410 aWriter.UniqueStringElement("string0"); 4411 aWriter.StartObjectElement(); 4412 { 4413 aWriter.UniqueStringProperty("p0", "prop"); 4414 aWriter.UniqueStringProperty("p1", "string0"); 4415 aWriter.UniqueStringProperty("p2", "prop"); 4416 } 4417 aWriter.EndObject(); 4418 aWriter.UniqueStringElement("string1"); 4419 aWriter.UniqueStringElement("string0"); 4420 aWriter.UniqueStringElement("prop"); 4421 aWriter.ResetUniqueStrings(); 4422 }, 4423 R"(0,{"p0":1,"p1":0,"p2":1},2,0,1)", R"("string0","prop","string1")"); 4424 4425 printf("TestUniqueJSONStrings done\n"); 4426 } 4427 4428 void StreamMarkers(const mozilla::ProfileChunkedBuffer& aBuffer, 4429 mozilla::baseprofiler::SpliceableJSONWriter& aWriter) { 4430 aWriter.StartArrayProperty("data"); 4431 { 4432 aBuffer.ReadEach([&](mozilla::ProfileBufferEntryReader& aEntryReader) { 4433 mozilla::ProfileBufferEntryKind entryKind = 4434 aEntryReader.ReadObject<mozilla::ProfileBufferEntryKind>(); 4435 MOZ_RELEASE_ASSERT(entryKind == mozilla::ProfileBufferEntryKind::Marker); 4436 4437 mozilla::base_profiler_markers_detail::DeserializeAfterKindAndStream( 4438 aEntryReader, 4439 [&](const mozilla::baseprofiler::BaseProfilerThreadId&) { 4440 return &aWriter; 4441 }, 4442 [&](mozilla::ProfileChunkedBuffer&) { 4443 aWriter.StringElement("Real backtrace would be here"); 4444 }, 4445 [&](mozilla::base_profiler_markers_detail::Streaming:: 4446 DeserializerTag) {}); 4447 }); 4448 } 4449 aWriter.EndArray(); 4450 } 4451 4452 void PrintMarkers(const mozilla::ProfileChunkedBuffer& aBuffer) { 4453 mozilla::baseprofiler::SpliceableJSONWriter writer( 4454 mozilla::MakeUnique<mozilla::baseprofiler::OStreamJSONWriteFunc>( 4455 std::cout), 4456 FailureLatchInfallibleSource::Singleton()); 4457 mozilla::baseprofiler::UniqueJSONStrings uniqueStrings{ 4458 FailureLatchInfallibleSource::Singleton()}; 4459 writer.SetUniqueStrings(uniqueStrings); 4460 writer.Start(); 4461 { 4462 StreamMarkers(aBuffer, writer); 4463 4464 writer.StartArrayProperty("stringTable"); 4465 { 4466 uniqueStrings.SpliceStringTableElements(writer); 4467 } 4468 writer.EndArray(); 4469 } 4470 writer.End(); 4471 writer.ResetUniqueStrings(); 4472 } 4473 4474 static void SubTestMarkerCategory( 4475 const mozilla::MarkerCategory& aMarkerCategory, 4476 const mozilla::baseprofiler::ProfilingCategoryPair& aProfilingCategoryPair, 4477 const mozilla::baseprofiler::ProfilingCategory& aProfilingCategory) { 4478 MOZ_RELEASE_ASSERT(aMarkerCategory.CategoryPair() == aProfilingCategoryPair, 4479 "Unexpected MarkerCategory::CategoryPair()"); 4480 4481 MOZ_RELEASE_ASSERT( 4482 mozilla::MarkerCategory(aProfilingCategoryPair).CategoryPair() == 4483 aProfilingCategoryPair, 4484 "MarkerCategory(<name>).CategoryPair() should return <name>"); 4485 4486 MOZ_RELEASE_ASSERT(aMarkerCategory.GetCategory() == aProfilingCategory, 4487 "Unexpected MarkerCategory::GetCategory()"); 4488 4489 mozilla::ProfileBufferChunkManagerSingle chunkManager(512); 4490 mozilla::ProfileChunkedBuffer buffer( 4491 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager); 4492 mozilla::ProfileBufferBlockIndex i = buffer.PutObject(aMarkerCategory); 4493 MOZ_RELEASE_ASSERT(i != mozilla::ProfileBufferBlockIndex{}, 4494 "Failed serialization"); 4495 buffer.ReadEach([&](mozilla::ProfileBufferEntryReader& aER, 4496 mozilla::ProfileBufferBlockIndex aIndex) { 4497 MOZ_RELEASE_ASSERT(aIndex == i, "Unexpected deserialization index"); 4498 const auto readCategory = aER.ReadObject<mozilla::MarkerCategory>(); 4499 MOZ_RELEASE_ASSERT(aER.RemainingBytes() == 0, 4500 "Unexpected extra serialized bytes"); 4501 MOZ_RELEASE_ASSERT(readCategory.CategoryPair() == aProfilingCategoryPair, 4502 "Incorrect deserialization value"); 4503 }); 4504 } 4505 4506 void TestMarkerCategory() { 4507 printf("TestMarkerCategory...\n"); 4508 4509 mozilla::ProfileBufferChunkManagerSingle chunkManager(512); 4510 mozilla::ProfileChunkedBuffer buffer( 4511 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager); 4512 4513 # define CATEGORY_ENUM_BEGIN_CATEGORY(name, labelAsString, color) 4514 # define CATEGORY_ENUM_SUBCATEGORY(supercategory, name, labelAsString) \ 4515 static_assert( \ 4516 std::is_same_v<decltype(mozilla::baseprofiler::category::name), \ 4517 const mozilla::MarkerCategory>, \ 4518 "baseprofiler::category::<name> should be a const MarkerCategory"); \ 4519 \ 4520 SubTestMarkerCategory( \ 4521 mozilla::baseprofiler::category::name, \ 4522 mozilla::baseprofiler::ProfilingCategoryPair::name, \ 4523 mozilla::baseprofiler::ProfilingCategory::supercategory); 4524 # define CATEGORY_ENUM_END_CATEGORY 4525 MOZ_PROFILING_CATEGORY_LIST(CATEGORY_ENUM_BEGIN_CATEGORY, 4526 CATEGORY_ENUM_SUBCATEGORY, 4527 CATEGORY_ENUM_END_CATEGORY) 4528 # undef CATEGORY_ENUM_BEGIN_CATEGORY 4529 # undef CATEGORY_ENUM_SUBCATEGORY 4530 # undef CATEGORY_ENUM_END_CATEGORY 4531 4532 printf("TestMarkerCategory done\n"); 4533 } 4534 4535 void TestMarkerThreadId() { 4536 printf("TestMarkerThreadId...\n"); 4537 4538 MOZ_RELEASE_ASSERT(MarkerThreadId{}.IsUnspecified()); 4539 MOZ_RELEASE_ASSERT(!MarkerThreadId::MainThread().IsUnspecified()); 4540 MOZ_RELEASE_ASSERT(!MarkerThreadId::CurrentThread().IsUnspecified()); 4541 4542 MOZ_RELEASE_ASSERT(!MarkerThreadId{ 4543 mozilla::baseprofiler::BaseProfilerThreadId::FromNumber(42)} 4544 .IsUnspecified()); 4545 MOZ_RELEASE_ASSERT( 4546 MarkerThreadId{ 4547 mozilla::baseprofiler::BaseProfilerThreadId::FromNumber(42)} 4548 .ThreadId() 4549 .ToNumber() == 42); 4550 4551 // We'll assume that this test runs in the main thread (which should be true 4552 // when called from the `main` function). 4553 MOZ_RELEASE_ASSERT(MarkerThreadId::MainThread().ThreadId() == 4554 mozilla::baseprofiler::profiler_main_thread_id()); 4555 4556 MOZ_RELEASE_ASSERT(MarkerThreadId::CurrentThread().ThreadId() == 4557 mozilla::baseprofiler::profiler_current_thread_id()); 4558 4559 MOZ_RELEASE_ASSERT(MarkerThreadId::CurrentThread().ThreadId() == 4560 mozilla::baseprofiler::profiler_main_thread_id()); 4561 4562 std::thread testThread([]() { 4563 MOZ_RELEASE_ASSERT(!MarkerThreadId::MainThread().IsUnspecified()); 4564 MOZ_RELEASE_ASSERT(!MarkerThreadId::CurrentThread().IsUnspecified()); 4565 4566 MOZ_RELEASE_ASSERT(MarkerThreadId::MainThread().ThreadId() == 4567 mozilla::baseprofiler::profiler_main_thread_id()); 4568 4569 MOZ_RELEASE_ASSERT(MarkerThreadId::CurrentThread().ThreadId() == 4570 mozilla::baseprofiler::profiler_current_thread_id()); 4571 4572 MOZ_RELEASE_ASSERT(MarkerThreadId::CurrentThread().ThreadId() != 4573 mozilla::baseprofiler::profiler_main_thread_id()); 4574 }); 4575 testThread.join(); 4576 4577 printf("TestMarkerThreadId done\n"); 4578 } 4579 4580 void TestMarkerNoPayload() { 4581 printf("TestMarkerNoPayload...\n"); 4582 4583 mozilla::ProfileBufferChunkManagerSingle chunkManager(512); 4584 mozilla::ProfileChunkedBuffer buffer( 4585 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager); 4586 4587 mozilla::ProfileBufferBlockIndex i0 = 4588 mozilla::baseprofiler::AddMarkerToBuffer( 4589 buffer, "literal", mozilla::baseprofiler::category::OTHER_Profiling); 4590 MOZ_RELEASE_ASSERT(i0); 4591 4592 const std::string dynamic = "dynamic"; 4593 mozilla::ProfileBufferBlockIndex i1 = 4594 mozilla::baseprofiler::AddMarkerToBuffer( 4595 buffer, dynamic, 4596 mozilla::baseprofiler::category::GRAPHICS_FlushingAsyncPaints, {}); 4597 MOZ_RELEASE_ASSERT(i1); 4598 MOZ_RELEASE_ASSERT(i1 > i0); 4599 4600 mozilla::ProfileBufferBlockIndex i2 = 4601 mozilla::baseprofiler::AddMarkerToBuffer( 4602 buffer, std::string_view("string_view"), 4603 mozilla::baseprofiler::category::GRAPHICS_FlushingAsyncPaints, {}); 4604 MOZ_RELEASE_ASSERT(i2); 4605 MOZ_RELEASE_ASSERT(i2 > i1); 4606 4607 # ifdef DEBUG 4608 buffer.Dump(); 4609 # endif 4610 4611 PrintMarkers(buffer); 4612 4613 printf("TestMarkerNoPayload done\n"); 4614 } 4615 4616 void TestUserMarker() { 4617 printf("TestUserMarker...\n"); 4618 4619 // User-defined marker type with text. 4620 // It's fine to define it right in the function where it's used. 4621 struct MarkerTypeTestMinimal { 4622 static constexpr Span<const char> MarkerTypeName() { 4623 return MakeStringSpan("test-minimal"); 4624 } 4625 static void StreamJSONMarkerData( 4626 mozilla::baseprofiler::SpliceableJSONWriter& aWriter, 4627 const std::string& aText) { 4628 aWriter.StringProperty("text", aText); 4629 } 4630 static mozilla::MarkerSchema MarkerTypeDisplay() { 4631 using MS = mozilla::MarkerSchema; 4632 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; 4633 schema.SetTooltipLabel("tooltip for test-minimal"); 4634 schema.AddKeyLabelFormat("text", "Text", MS::Format::String, 4635 MS::PayloadFlags::Searchable); 4636 return schema; 4637 } 4638 }; 4639 4640 mozilla::ProfileBufferChunkManagerSingle chunkManager(1024); 4641 mozilla::ProfileChunkedBuffer buffer( 4642 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager); 4643 4644 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4645 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling, {}, 4646 MarkerTypeTestMinimal{}, std::string("payload text"))); 4647 4648 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4649 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling, 4650 mozilla::MarkerThreadId( 4651 mozilla::baseprofiler::BaseProfilerThreadId::FromNumber(123)), 4652 MarkerTypeTestMinimal{}, std::string("ThreadId(123)"))); 4653 4654 auto start = mozilla::TimeStamp::Now(); 4655 4656 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4657 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling, 4658 mozilla::MarkerTiming::InstantAt(start), MarkerTypeTestMinimal{}, 4659 std::string("InstantAt(start)"))); 4660 4661 auto then = mozilla::TimeStamp::Now(); 4662 4663 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4664 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling, 4665 mozilla::MarkerTiming::IntervalStart(start), MarkerTypeTestMinimal{}, 4666 std::string("IntervalStart(start)"))); 4667 4668 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4669 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling, 4670 mozilla::MarkerTiming::IntervalEnd(then), MarkerTypeTestMinimal{}, 4671 std::string("IntervalEnd(then)"))); 4672 4673 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4674 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling, 4675 mozilla::MarkerTiming::Interval(start, then), MarkerTypeTestMinimal{}, 4676 std::string("Interval(start, then)"))); 4677 4678 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4679 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling, 4680 mozilla::MarkerTiming::IntervalUntilNowFrom(start), 4681 MarkerTypeTestMinimal{}, std::string("IntervalUntilNowFrom(start)"))); 4682 4683 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4684 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling, 4685 mozilla::MarkerStack::NoStack(), MarkerTypeTestMinimal{}, 4686 std::string("NoStack"))); 4687 // Note: We cannot test stack-capture here, because the profiler is not 4688 // initialized. 4689 4690 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4691 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling, 4692 mozilla::MarkerInnerWindowId(123), MarkerTypeTestMinimal{}, 4693 std::string("InnerWindowId(123)"))); 4694 4695 # ifdef DEBUG 4696 buffer.Dump(); 4697 # endif 4698 4699 PrintMarkers(buffer); 4700 4701 printf("TestUserMarker done\n"); 4702 } 4703 4704 void TestPredefinedMarkers() { 4705 printf("TestPredefinedMarkers...\n"); 4706 4707 mozilla::ProfileBufferChunkManagerSingle chunkManager(1024); 4708 mozilla::ProfileChunkedBuffer buffer( 4709 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager); 4710 4711 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4712 buffer, std::string_view("stackmarker"), 4713 mozilla::baseprofiler::category::OTHER, {}, 4714 mozilla::baseprofiler::markers::StackMarker{})); 4715 4716 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4717 buffer, std::string_view("text"), mozilla::baseprofiler::category::OTHER, 4718 {}, mozilla::baseprofiler::markers::TextMarker{}, "text text")); 4719 4720 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4721 buffer, std::string_view("media"), mozilla::baseprofiler::category::OTHER, 4722 {}, mozilla::baseprofiler::markers::MediaSampleMarker{}, 123, 456, 789)); 4723 4724 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer( 4725 buffer, std::string_view("media"), mozilla::baseprofiler::category::OTHER, 4726 {}, mozilla::baseprofiler::markers::VideoFallingBehindMarker{}, 123, 4727 456)); 4728 4729 # ifdef DEBUG 4730 buffer.Dump(); 4731 # endif 4732 4733 PrintMarkers(buffer); 4734 4735 printf("TestPredefinedMarkers done\n"); 4736 } 4737 4738 void TestProfilerMarkers() { 4739 printf( 4740 "TestProfilerMarkers -- pid: %" PRIu64 ", tid: %" PRIu64 "\n", 4741 uint64_t(mozilla::baseprofiler::profiler_current_process_id().ToNumber()), 4742 uint64_t(mozilla::baseprofiler::profiler_current_thread_id().ToNumber())); 4743 // ::SleepMilli(10000); 4744 4745 TestUniqueJSONStrings(); 4746 TestMarkerCategory(); 4747 TestMarkerThreadId(); 4748 TestMarkerNoPayload(); 4749 TestUserMarker(); 4750 TestPredefinedMarkers(); 4751 4752 printf("TestProfilerMarkers done\n"); 4753 } 4754 4755 #else // MOZ_GECKO_PROFILER 4756 4757 // Testing that macros are still #defined (but do nothing) when 4758 // MOZ_GECKO_PROFILER is disabled. 4759 void TestProfiler() { 4760 // These don't need to make sense, we just want to know that they're defined 4761 // and don't do anything. 4762 4763 # ifndef AUTO_BASE_PROFILER_INIT 4764 # error AUTO_BASE_PROFILER_INIT not #defined 4765 # endif // AUTO_BASE_PROFILER_INIT 4766 AUTO_BASE_PROFILER_INIT; 4767 4768 # ifndef AUTO_BASE_PROFILER_MARKER_TEXT 4769 # error AUTO_BASE_PROFILER_MARKER_TEXT not #defined 4770 # endif // AUTO_BASE_PROFILER_MARKER_TEXT 4771 4772 # ifndef AUTO_BASE_PROFILER_LABEL 4773 # error AUTO_BASE_PROFILER_LABEL not #defined 4774 # endif // AUTO_BASE_PROFILER_LABEL 4775 4776 # ifndef AUTO_BASE_PROFILER_THREAD_SLEEP 4777 # error AUTO_BASE_PROFILER_THREAD_SLEEP not #defined 4778 # endif // AUTO_BASE_PROFILER_THREAD_SLEEP 4779 AUTO_BASE_PROFILER_THREAD_SLEEP; 4780 4781 # ifndef BASE_PROFILER_MARKER_UNTYPED 4782 # error BASE_PROFILER_MARKER_UNTYPED not #defined 4783 # endif // BASE_PROFILER_MARKER_UNTYPED 4784 4785 # ifndef BASE_PROFILER_MARKER 4786 # error BASE_PROFILER_MARKER not #defined 4787 # endif // BASE_PROFILER_MARKER 4788 4789 # ifndef BASE_PROFILER_MARKER_TEXT 4790 # error BASE_PROFILER_MARKER_TEXT not #defined 4791 # endif // BASE_PROFILER_MARKER_TEXT 4792 4793 MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_get_backtrace(), 4794 "profiler_get_backtrace should return nullptr"); 4795 mozilla::ProfileChunkedBuffer buffer( 4796 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex); 4797 MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_capture_backtrace_into( 4798 buffer, mozilla::StackCaptureOptions::Full), 4799 "profiler_capture_backtrace_into should return false"); 4800 MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_capture_backtrace(), 4801 "profiler_capture_backtrace should return nullptr"); 4802 } 4803 4804 // Testing that macros are still #defined (but do nothing) when 4805 // MOZ_GECKO_PROFILER is disabled. 4806 void TestProfilerMarkers() { 4807 // These don't need to make sense, we just want to know that they're defined 4808 // and don't do anything. 4809 } 4810 4811 #endif // MOZ_GECKO_PROFILER else 4812 4813 #if defined(XP_WIN) 4814 int wmain() 4815 #else 4816 int main() 4817 #endif // defined(XP_WIN) 4818 { 4819 #ifdef MOZ_GECKO_PROFILER 4820 printf("BaseTestProfiler -- pid: %" PRIu64 ", tid: %" PRIu64 "\n", 4821 uint64_t(baseprofiler::profiler_current_process_id().ToNumber()), 4822 uint64_t(baseprofiler::profiler_current_thread_id().ToNumber())); 4823 // ::SleepMilli(10000); 4824 #endif // MOZ_GECKO_PROFILER 4825 4826 TestFailureLatch(); 4827 TestProfilerUtils(); 4828 TestBaseAndProfilerDetail(); 4829 TestSharedMutex(); 4830 TestProportionValue(); 4831 TestProgressLogger(); 4832 // Note that there are two `TestProfiler{,Markers}` functions above, depending 4833 // on whether MOZ_GECKO_PROFILER is #defined. 4834 { 4835 printf("profiler_init()...\n"); 4836 AUTO_BASE_PROFILER_INIT; 4837 4838 TestProfiler(); 4839 TestProfilerMarkers(); 4840 } 4841 4842 return 0; 4843 }