Bug 1858509 add thread-safety annotations around MediaSourceDemuxer::mMonitor r=alwu
[gecko.git] / mozglue / tests / TestBaseProfiler.cpp
blob03b2076ebcfd363870e3401287715e340c496844
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/. */
7 #include "BaseProfiler.h"
9 #include "mozilla/Attributes.h"
10 #include "mozilla/BaseAndGeckoProfilerDetail.h"
11 #include "mozilla/BaseProfileJSONWriter.h"
12 #include "mozilla/BaseProfilerDetail.h"
13 #include "mozilla/FailureLatch.h"
14 #include "mozilla/FloatingPoint.h"
15 #include "mozilla/NotNull.h"
16 #include "mozilla/ProgressLogger.h"
17 #include "mozilla/ProportionValue.h"
19 #ifdef MOZ_GECKO_PROFILER
20 # include "mozilla/BaseProfilerMarkerTypes.h"
21 # include "mozilla/BlocksRingBuffer.h"
22 # include "mozilla/leb128iterator.h"
23 # include "mozilla/ModuloBuffer.h"
24 # include "mozilla/mozalloc.h"
25 # include "mozilla/PowerOfTwo.h"
26 # include "mozilla/ProfileBufferChunk.h"
27 # include "mozilla/ProfileBufferChunkManagerSingle.h"
28 # include "mozilla/ProfileBufferChunkManagerWithLocalLimit.h"
29 # include "mozilla/ProfileBufferControlledChunkManager.h"
30 # include "mozilla/ProfileChunkedBuffer.h"
31 # include "mozilla/Vector.h"
32 #endif // MOZ_GECKO_PROFILER
34 #if defined(_MSC_VER) || defined(__MINGW32__)
35 # include <windows.h>
36 # include <mmsystem.h>
37 # include <process.h>
38 #else
39 # include <errno.h>
40 # include <time.h>
41 #endif
43 #include <algorithm>
44 #include <atomic>
45 #include <iostream>
46 #include <random>
47 #include <thread>
48 #include <type_traits>
49 #include <utility>
51 void TestFailureLatch() {
52 printf("TestFailureLatch...\n");
54 // Test infallible latch.
56 mozilla::FailureLatchInfallibleSource& infallibleLatch =
57 mozilla::FailureLatchInfallibleSource::Singleton();
59 MOZ_RELEASE_ASSERT(!infallibleLatch.Fallible());
60 MOZ_RELEASE_ASSERT(!infallibleLatch.Failed());
61 MOZ_RELEASE_ASSERT(!infallibleLatch.GetFailure());
62 MOZ_RELEASE_ASSERT(&infallibleLatch.SourceFailureLatch() ==
63 &mozilla::FailureLatchInfallibleSource::Singleton());
64 MOZ_RELEASE_ASSERT(&std::as_const(infallibleLatch).SourceFailureLatch() ==
65 &mozilla::FailureLatchInfallibleSource::Singleton());
68 // Test failure latch basic functions.
70 mozilla::FailureLatchSource failureLatch;
72 MOZ_RELEASE_ASSERT(failureLatch.Fallible());
73 MOZ_RELEASE_ASSERT(!failureLatch.Failed());
74 MOZ_RELEASE_ASSERT(!failureLatch.GetFailure());
75 MOZ_RELEASE_ASSERT(&failureLatch.SourceFailureLatch() == &failureLatch);
76 MOZ_RELEASE_ASSERT(&std::as_const(failureLatch).SourceFailureLatch() ==
77 &failureLatch);
79 failureLatch.SetFailure("error");
81 MOZ_RELEASE_ASSERT(failureLatch.Fallible());
82 MOZ_RELEASE_ASSERT(failureLatch.Failed());
83 MOZ_RELEASE_ASSERT(failureLatch.GetFailure());
84 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
86 failureLatch.SetFailure("later error");
88 MOZ_RELEASE_ASSERT(failureLatch.Fallible());
89 MOZ_RELEASE_ASSERT(failureLatch.Failed());
90 MOZ_RELEASE_ASSERT(failureLatch.GetFailure());
91 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
94 // Test SetFailureFrom.
96 mozilla::FailureLatchSource failureLatch;
98 MOZ_RELEASE_ASSERT(!failureLatch.Failed());
99 failureLatch.SetFailureFrom(failureLatch);
100 MOZ_RELEASE_ASSERT(!failureLatch.Failed());
101 MOZ_RELEASE_ASSERT(!failureLatch.GetFailure());
103 // SetFailureFrom with no error.
105 mozilla::FailureLatchSource failureLatchInnerOk;
106 MOZ_RELEASE_ASSERT(!failureLatchInnerOk.Failed());
107 MOZ_RELEASE_ASSERT(!failureLatchInnerOk.GetFailure());
109 MOZ_RELEASE_ASSERT(!failureLatch.Failed());
110 failureLatch.SetFailureFrom(failureLatchInnerOk);
111 MOZ_RELEASE_ASSERT(!failureLatch.Failed());
113 MOZ_RELEASE_ASSERT(!failureLatchInnerOk.Failed());
114 MOZ_RELEASE_ASSERT(!failureLatchInnerOk.GetFailure());
116 MOZ_RELEASE_ASSERT(!failureLatch.Failed());
117 MOZ_RELEASE_ASSERT(!failureLatch.GetFailure());
119 // SetFailureFrom with error.
121 mozilla::FailureLatchSource failureLatchInnerError;
122 MOZ_RELEASE_ASSERT(!failureLatchInnerError.Failed());
123 MOZ_RELEASE_ASSERT(!failureLatchInnerError.GetFailure());
125 failureLatchInnerError.SetFailure("inner error");
126 MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
127 MOZ_RELEASE_ASSERT(
128 strcmp(failureLatchInnerError.GetFailure(), "inner error") == 0);
130 MOZ_RELEASE_ASSERT(!failureLatch.Failed());
131 failureLatch.SetFailureFrom(failureLatchInnerError);
132 MOZ_RELEASE_ASSERT(failureLatch.Failed());
134 MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
135 MOZ_RELEASE_ASSERT(
136 strcmp(failureLatchInnerError.GetFailure(), "inner error") == 0);
138 MOZ_RELEASE_ASSERT(failureLatch.Failed());
139 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0);
141 failureLatch.SetFailureFrom(failureLatch);
142 MOZ_RELEASE_ASSERT(failureLatch.Failed());
143 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0);
145 // SetFailureFrom with error again, ignored.
147 mozilla::FailureLatchSource failureLatchInnerError;
148 failureLatchInnerError.SetFailure("later inner error");
149 MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
150 MOZ_RELEASE_ASSERT(strcmp(failureLatchInnerError.GetFailure(),
151 "later inner error") == 0);
153 MOZ_RELEASE_ASSERT(failureLatch.Failed());
154 failureLatch.SetFailureFrom(failureLatchInnerError);
155 MOZ_RELEASE_ASSERT(failureLatch.Failed());
157 MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
158 MOZ_RELEASE_ASSERT(strcmp(failureLatchInnerError.GetFailure(),
159 "later inner error") == 0);
161 MOZ_RELEASE_ASSERT(failureLatch.Failed());
162 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0);
165 // Test FAILURELATCH_IMPL_PROXY
167 class Proxy final : public mozilla::FailureLatch {
168 public:
169 explicit Proxy(mozilla::FailureLatch& aFailureLatch)
170 : mFailureLatch(WrapNotNull(&aFailureLatch)) {}
172 void Set(mozilla::FailureLatch& aFailureLatch) {
173 mFailureLatch = WrapNotNull(&aFailureLatch);
176 FAILURELATCH_IMPL_PROXY(*mFailureLatch)
178 private:
179 mozilla::NotNull<mozilla::FailureLatch*> mFailureLatch;
182 Proxy proxy{mozilla::FailureLatchInfallibleSource::Singleton()};
184 MOZ_RELEASE_ASSERT(!proxy.Fallible());
185 MOZ_RELEASE_ASSERT(!proxy.Failed());
186 MOZ_RELEASE_ASSERT(!proxy.GetFailure());
187 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
188 &mozilla::FailureLatchInfallibleSource::Singleton());
189 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
190 &mozilla::FailureLatchInfallibleSource::Singleton());
192 // Error from proxy.
194 mozilla::FailureLatchSource failureLatch;
195 proxy.Set(failureLatch);
196 MOZ_RELEASE_ASSERT(proxy.Fallible());
197 MOZ_RELEASE_ASSERT(!proxy.Failed());
198 MOZ_RELEASE_ASSERT(!proxy.GetFailure());
199 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
200 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
201 &failureLatch);
203 proxy.SetFailure("error");
204 MOZ_RELEASE_ASSERT(proxy.Failed());
205 MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
206 MOZ_RELEASE_ASSERT(failureLatch.Failed());
207 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
209 // Don't forget to stop pointing at soon-to-be-destroyed object.
210 proxy.Set(mozilla::FailureLatchInfallibleSource::Singleton());
213 // Error from proxy's origin.
215 mozilla::FailureLatchSource failureLatch;
216 proxy.Set(failureLatch);
217 MOZ_RELEASE_ASSERT(proxy.Fallible());
218 MOZ_RELEASE_ASSERT(!proxy.Failed());
219 MOZ_RELEASE_ASSERT(!proxy.GetFailure());
220 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
221 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
222 &failureLatch);
224 failureLatch.SetFailure("error");
225 MOZ_RELEASE_ASSERT(proxy.Failed());
226 MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
227 MOZ_RELEASE_ASSERT(failureLatch.Failed());
228 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
230 // Don't forget to stop pointing at soon-to-be-destroyed object.
231 proxy.Set(mozilla::FailureLatchInfallibleSource::Singleton());
234 MOZ_RELEASE_ASSERT(!proxy.Fallible());
235 MOZ_RELEASE_ASSERT(!proxy.Failed());
236 MOZ_RELEASE_ASSERT(!proxy.GetFailure());
237 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
238 &mozilla::FailureLatchInfallibleSource::Singleton());
239 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
240 &mozilla::FailureLatchInfallibleSource::Singleton());
243 // Test FAILURELATCH_IMPL_PROXY_OR_INFALLIBLE
245 class ProxyOrNull final : public mozilla::FailureLatch {
246 public:
247 ProxyOrNull() = default;
249 void Set(mozilla::FailureLatch* aFailureLatchOrNull) {
250 mFailureLatchOrNull = aFailureLatchOrNull;
253 FAILURELATCH_IMPL_PROXY_OR_INFALLIBLE(mFailureLatchOrNull, ProxyOrNull)
255 private:
256 mozilla::FailureLatch* mFailureLatchOrNull = nullptr;
259 ProxyOrNull proxy;
261 MOZ_RELEASE_ASSERT(!proxy.Fallible());
262 MOZ_RELEASE_ASSERT(!proxy.Failed());
263 MOZ_RELEASE_ASSERT(!proxy.GetFailure());
264 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
265 &mozilla::FailureLatchInfallibleSource::Singleton());
266 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
267 &mozilla::FailureLatchInfallibleSource::Singleton());
269 // Error from proxy.
271 mozilla::FailureLatchSource failureLatch;
272 proxy.Set(&failureLatch);
273 MOZ_RELEASE_ASSERT(proxy.Fallible());
274 MOZ_RELEASE_ASSERT(!proxy.Failed());
275 MOZ_RELEASE_ASSERT(!proxy.GetFailure());
276 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
277 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
278 &failureLatch);
280 proxy.SetFailure("error");
281 MOZ_RELEASE_ASSERT(proxy.Failed());
282 MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
283 MOZ_RELEASE_ASSERT(failureLatch.Failed());
284 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
286 // Don't forget to stop pointing at soon-to-be-destroyed object.
287 proxy.Set(nullptr);
290 // Error from proxy's origin.
292 mozilla::FailureLatchSource failureLatch;
293 proxy.Set(&failureLatch);
294 MOZ_RELEASE_ASSERT(proxy.Fallible());
295 MOZ_RELEASE_ASSERT(!proxy.Failed());
296 MOZ_RELEASE_ASSERT(!proxy.GetFailure());
297 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
298 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
299 &failureLatch);
301 failureLatch.SetFailure("error");
302 MOZ_RELEASE_ASSERT(proxy.Failed());
303 MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
304 MOZ_RELEASE_ASSERT(failureLatch.Failed());
305 MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
307 // Don't forget to stop pointing at soon-to-be-destroyed object.
308 proxy.Set(nullptr);
311 MOZ_RELEASE_ASSERT(!proxy.Fallible());
312 MOZ_RELEASE_ASSERT(!proxy.Failed());
313 MOZ_RELEASE_ASSERT(!proxy.GetFailure());
314 MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
315 &mozilla::FailureLatchInfallibleSource::Singleton());
316 MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
317 &mozilla::FailureLatchInfallibleSource::Singleton());
320 printf("TestFailureLatch done\n");
323 void TestProfilerUtils() {
324 printf("TestProfilerUtils...\n");
327 using mozilla::baseprofiler::BaseProfilerProcessId;
328 using Number = BaseProfilerProcessId::NumberType;
329 static constexpr Number scMaxNumber = std::numeric_limits<Number>::max();
331 static_assert(
332 BaseProfilerProcessId{}.ToNumber() == 0,
333 "These tests assume that the unspecified process id number is 0; "
334 "if this fails, please update these tests accordingly");
336 static_assert(!BaseProfilerProcessId{}.IsSpecified());
337 static_assert(!BaseProfilerProcessId::FromNumber(0).IsSpecified());
338 static_assert(BaseProfilerProcessId::FromNumber(1).IsSpecified());
339 static_assert(BaseProfilerProcessId::FromNumber(123).IsSpecified());
340 static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber).IsSpecified());
342 static_assert(BaseProfilerProcessId::FromNumber(Number(1)).ToNumber() ==
343 Number(1));
344 static_assert(BaseProfilerProcessId::FromNumber(Number(123)).ToNumber() ==
345 Number(123));
346 static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber).ToNumber() ==
347 scMaxNumber);
349 static_assert(BaseProfilerProcessId{} == BaseProfilerProcessId{});
350 static_assert(BaseProfilerProcessId::FromNumber(Number(123)) ==
351 BaseProfilerProcessId::FromNumber(Number(123)));
352 static_assert(BaseProfilerProcessId{} !=
353 BaseProfilerProcessId::FromNumber(Number(123)));
354 static_assert(BaseProfilerProcessId::FromNumber(Number(123)) !=
355 BaseProfilerProcessId{});
356 static_assert(BaseProfilerProcessId::FromNumber(Number(123)) !=
357 BaseProfilerProcessId::FromNumber(scMaxNumber));
358 static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber) !=
359 BaseProfilerProcessId::FromNumber(Number(123)));
361 // Verify trivial-copyability by memcpy'ing to&from same-size storage.
362 static_assert(std::is_trivially_copyable_v<BaseProfilerProcessId>);
363 BaseProfilerProcessId pid;
364 MOZ_RELEASE_ASSERT(!pid.IsSpecified());
365 Number pidStorage;
366 static_assert(sizeof(pidStorage) == sizeof(pid));
367 // Copy from BaseProfilerProcessId to storage. Note: We cannot assume that
368 // this is equal to what ToNumber() gives us. All we can do is verify that
369 // copying from storage back to BaseProfilerProcessId works as expected.
370 std::memcpy(&pidStorage, &pid, sizeof(pidStorage));
371 BaseProfilerProcessId pid2 = BaseProfilerProcessId::FromNumber(2);
372 MOZ_RELEASE_ASSERT(pid2.IsSpecified());
373 std::memcpy(&pid2, &pidStorage, sizeof(pid));
374 MOZ_RELEASE_ASSERT(!pid2.IsSpecified());
376 pid = BaseProfilerProcessId::FromNumber(123);
377 std::memcpy(&pidStorage, &pid, sizeof(pidStorage));
378 pid2 = BaseProfilerProcessId{};
379 MOZ_RELEASE_ASSERT(!pid2.IsSpecified());
380 std::memcpy(&pid2, &pidStorage, sizeof(pid));
381 MOZ_RELEASE_ASSERT(pid2.IsSpecified());
382 MOZ_RELEASE_ASSERT(pid2.ToNumber() == 123);
384 // No conversions to/from numbers.
385 static_assert(!std::is_constructible_v<BaseProfilerProcessId, Number>);
386 static_assert(!std::is_assignable_v<BaseProfilerProcessId, Number>);
387 static_assert(!std::is_constructible_v<Number, BaseProfilerProcessId>);
388 static_assert(!std::is_assignable_v<Number, BaseProfilerProcessId>);
390 static_assert(
391 std::is_same_v<
392 decltype(mozilla::baseprofiler::profiler_current_process_id()),
393 BaseProfilerProcessId>);
394 MOZ_RELEASE_ASSERT(
395 mozilla::baseprofiler::profiler_current_process_id().IsSpecified());
399 mozilla::baseprofiler::profiler_init_main_thread_id();
401 using mozilla::baseprofiler::BaseProfilerThreadId;
402 using Number = BaseProfilerThreadId::NumberType;
403 static constexpr Number scMaxNumber = std::numeric_limits<Number>::max();
405 static_assert(
406 BaseProfilerThreadId{}.ToNumber() == 0,
407 "These tests assume that the unspecified thread id number is 0; "
408 "if this fails, please update these tests accordingly");
410 static_assert(!BaseProfilerThreadId{}.IsSpecified());
411 static_assert(!BaseProfilerThreadId::FromNumber(0).IsSpecified());
412 static_assert(BaseProfilerThreadId::FromNumber(1).IsSpecified());
413 static_assert(BaseProfilerThreadId::FromNumber(123).IsSpecified());
414 static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber).IsSpecified());
416 static_assert(BaseProfilerThreadId::FromNumber(Number(1)).ToNumber() ==
417 Number(1));
418 static_assert(BaseProfilerThreadId::FromNumber(Number(123)).ToNumber() ==
419 Number(123));
420 static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber).ToNumber() ==
421 scMaxNumber);
423 static_assert(BaseProfilerThreadId{} == BaseProfilerThreadId{});
424 static_assert(BaseProfilerThreadId::FromNumber(Number(123)) ==
425 BaseProfilerThreadId::FromNumber(Number(123)));
426 static_assert(BaseProfilerThreadId{} !=
427 BaseProfilerThreadId::FromNumber(Number(123)));
428 static_assert(BaseProfilerThreadId::FromNumber(Number(123)) !=
429 BaseProfilerThreadId{});
430 static_assert(BaseProfilerThreadId::FromNumber(Number(123)) !=
431 BaseProfilerThreadId::FromNumber(scMaxNumber));
432 static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber) !=
433 BaseProfilerThreadId::FromNumber(Number(123)));
435 // Verify trivial-copyability by memcpy'ing to&from same-size storage.
436 static_assert(std::is_trivially_copyable_v<BaseProfilerThreadId>);
437 BaseProfilerThreadId tid;
438 MOZ_RELEASE_ASSERT(!tid.IsSpecified());
439 Number tidStorage;
440 static_assert(sizeof(tidStorage) == sizeof(tid));
441 // Copy from BaseProfilerThreadId to storage. Note: We cannot assume that
442 // this is equal to what ToNumber() gives us. All we can do is verify that
443 // copying from storage back to BaseProfilerThreadId works as expected.
444 std::memcpy(&tidStorage, &tid, sizeof(tidStorage));
445 BaseProfilerThreadId tid2 = BaseProfilerThreadId::FromNumber(2);
446 MOZ_RELEASE_ASSERT(tid2.IsSpecified());
447 std::memcpy(&tid2, &tidStorage, sizeof(tid));
448 MOZ_RELEASE_ASSERT(!tid2.IsSpecified());
450 tid = BaseProfilerThreadId::FromNumber(Number(123));
451 std::memcpy(&tidStorage, &tid, sizeof(tidStorage));
452 tid2 = BaseProfilerThreadId{};
453 MOZ_RELEASE_ASSERT(!tid2.IsSpecified());
454 std::memcpy(&tid2, &tidStorage, sizeof(tid));
455 MOZ_RELEASE_ASSERT(tid2.IsSpecified());
456 MOZ_RELEASE_ASSERT(tid2.ToNumber() == Number(123));
458 // No conversions to/from numbers.
459 static_assert(!std::is_constructible_v<BaseProfilerThreadId, Number>);
460 static_assert(!std::is_assignable_v<BaseProfilerThreadId, Number>);
461 static_assert(!std::is_constructible_v<Number, BaseProfilerThreadId>);
462 static_assert(!std::is_assignable_v<Number, BaseProfilerThreadId>);
464 static_assert(std::is_same_v<
465 decltype(mozilla::baseprofiler::profiler_current_thread_id()),
466 BaseProfilerThreadId>);
467 BaseProfilerThreadId mainTestThreadId =
468 mozilla::baseprofiler::profiler_current_thread_id();
469 MOZ_RELEASE_ASSERT(mainTestThreadId.IsSpecified());
471 BaseProfilerThreadId mainThreadId =
472 mozilla::baseprofiler::profiler_main_thread_id();
473 MOZ_RELEASE_ASSERT(mainThreadId.IsSpecified());
475 MOZ_RELEASE_ASSERT(mainThreadId == mainTestThreadId,
476 "Test should run on the main thread");
477 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::profiler_is_main_thread());
479 std::thread testThread([&]() {
480 const BaseProfilerThreadId testThreadId =
481 mozilla::baseprofiler::profiler_current_thread_id();
482 MOZ_RELEASE_ASSERT(testThreadId.IsSpecified());
483 MOZ_RELEASE_ASSERT(testThreadId != mainThreadId);
484 MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_is_main_thread());
486 testThread.join();
489 // No conversions between processes and threads.
490 static_assert(
491 !std::is_constructible_v<mozilla::baseprofiler::BaseProfilerThreadId,
492 mozilla::baseprofiler::BaseProfilerProcessId>);
493 static_assert(
494 !std::is_assignable_v<mozilla::baseprofiler::BaseProfilerThreadId,
495 mozilla::baseprofiler::BaseProfilerProcessId>);
496 static_assert(
497 !std::is_constructible_v<mozilla::baseprofiler::BaseProfilerProcessId,
498 mozilla::baseprofiler::BaseProfilerThreadId>);
499 static_assert(
500 !std::is_assignable_v<mozilla::baseprofiler::BaseProfilerProcessId,
501 mozilla::baseprofiler::BaseProfilerThreadId>);
503 printf("TestProfilerUtils done\n");
506 void TestBaseAndProfilerDetail() {
507 printf("TestBaseAndProfilerDetail...\n");
510 using mozilla::profiler::detail::FilterHasPid;
512 const auto pid123 =
513 mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(123);
514 MOZ_RELEASE_ASSERT(FilterHasPid("pid:123", pid123));
515 MOZ_RELEASE_ASSERT(!FilterHasPid("", pid123));
516 MOZ_RELEASE_ASSERT(!FilterHasPid(" ", pid123));
517 MOZ_RELEASE_ASSERT(!FilterHasPid("123", pid123));
518 MOZ_RELEASE_ASSERT(!FilterHasPid("pid", pid123));
519 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:", pid123));
520 MOZ_RELEASE_ASSERT(!FilterHasPid("pid=123", pid123));
521 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:123 ", pid123));
522 MOZ_RELEASE_ASSERT(!FilterHasPid("pid: 123", pid123));
523 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0123", pid123));
524 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0000000000000000000000123", pid123));
525 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:12", pid123));
526 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:1234", pid123));
527 MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0", pid123));
529 using PidNumber = mozilla::baseprofiler::BaseProfilerProcessId::NumberType;
530 const PidNumber maxNumber = std::numeric_limits<PidNumber>::max();
531 const auto maxPid =
532 mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(maxNumber);
533 const std::string maxPidString = "pid:" + std::to_string(maxNumber);
534 MOZ_RELEASE_ASSERT(FilterHasPid(maxPidString.c_str(), maxPid));
536 const std::string tooBigPidString = maxPidString + "0";
537 MOZ_RELEASE_ASSERT(!FilterHasPid(tooBigPidString.c_str(), maxPid));
541 using mozilla::profiler::detail::FiltersExcludePid;
542 const auto pid123 =
543 mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(123);
545 MOZ_RELEASE_ASSERT(
546 !FiltersExcludePid(mozilla::Span<const char*>{}, pid123));
549 const char* const filters[] = {"main"};
550 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
554 const char* const filters[] = {"main", "pid:123"};
555 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
559 const char* const filters[] = {"main", "pid:456"};
560 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
564 const char* const filters[] = {"pid:123"};
565 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
569 const char* const filters[] = {"pid:123", "pid:456"};
570 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
574 const char* const filters[] = {"pid:456", "pid:123"};
575 MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
579 const char* const filters[] = {"pid:456"};
580 MOZ_RELEASE_ASSERT(FiltersExcludePid(filters, pid123));
584 const char* const filters[] = {"pid:456", "pid:789"};
585 MOZ_RELEASE_ASSERT(FiltersExcludePid(filters, pid123));
589 printf("TestBaseAndProfilerDetail done\n");
592 void TestSharedMutex() {
593 printf("TestSharedMutex...\n");
595 mozilla::baseprofiler::detail::BaseProfilerSharedMutex sm;
597 // First round of minimal tests in this thread.
599 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
601 sm.LockExclusive();
602 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
603 sm.UnlockExclusive();
604 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
606 sm.LockShared();
607 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
608 sm.UnlockShared();
609 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
612 mozilla::baseprofiler::detail::BaseProfilerAutoLockExclusive exclusiveLock{
613 sm};
614 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
616 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
619 mozilla::baseprofiler::detail::BaseProfilerAutoLockShared sharedLock{sm};
620 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
622 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
624 // The following will run actions between two threads, to verify that
625 // exclusive and shared locks work as expected.
627 // These actions will happen from top to bottom.
628 // This will test all possible lock interactions.
629 enum NextAction { // State of the lock:
630 t1Starting, // (x=exclusive, s=shared, ?=blocked)
631 t2Starting, // t1 t2
632 t1LockExclusive, // x
633 t2LockExclusiveAndBlock, // x x? - Can't have two exclusives.
634 t1UnlockExclusive, // x
635 t2UnblockedAfterT1Unlock, // x
636 t1LockSharedAndBlock, // s? x - Can't have shared during excl
637 t2UnlockExclusive, // s
638 t1UnblockedAfterT2Unlock, // s
639 t2LockShared, // s s - Can have multiple shared locks
640 t1UnlockShared, // s
641 t2StillLockedShared, // s
642 t1LockExclusiveAndBlock, // x? s - Can't have excl during shared
643 t2UnlockShared, // x
644 t1UnblockedAfterT2UnlockShared, // x
645 t2CheckAfterT1Lock, // x
646 t1LastUnlockExclusive, // (unlocked)
647 done
650 // Each thread will repeatedly read this `nextAction`, and run actions that
651 // target it...
652 std::atomic<NextAction> nextAction{static_cast<NextAction>(0)};
653 // ... and advance to the next available action (which should usually be for
654 // the other thread).
655 auto AdvanceAction = [&nextAction]() {
656 MOZ_RELEASE_ASSERT(nextAction <= done);
657 nextAction = static_cast<NextAction>(static_cast<int>(nextAction) + 1);
660 std::thread t1{[&]() {
661 for (;;) {
662 switch (nextAction) {
663 case t1Starting:
664 AdvanceAction();
665 break;
666 case t1LockExclusive:
667 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
668 sm.LockExclusive();
669 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
670 AdvanceAction();
671 break;
672 case t1UnlockExclusive:
673 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
674 // Advance first, before unlocking, so that t2 sees the new state.
675 AdvanceAction();
676 sm.UnlockExclusive();
677 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
678 break;
679 case t1LockSharedAndBlock:
680 // Advance action before attempting to lock after t2's exclusive lock.
681 AdvanceAction();
682 sm.LockShared();
683 // We will only acquire the lock after t1 unlocks.
684 MOZ_RELEASE_ASSERT(nextAction == t1UnblockedAfterT2Unlock);
685 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
686 AdvanceAction();
687 break;
688 case t1UnlockShared:
689 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
690 // Advance first, before unlocking, so that t2 sees the new state.
691 AdvanceAction();
692 sm.UnlockShared();
693 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
694 break;
695 case t1LockExclusiveAndBlock:
696 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
697 // Advance action before attempting to lock after t2's shared lock.
698 AdvanceAction();
699 sm.LockExclusive();
700 // We will only acquire the lock after t2 unlocks.
701 MOZ_RELEASE_ASSERT(nextAction == t1UnblockedAfterT2UnlockShared);
702 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
703 AdvanceAction();
704 break;
705 case t1LastUnlockExclusive:
706 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
707 // Advance first, before unlocking, so that t2 sees the new state.
708 AdvanceAction();
709 sm.UnlockExclusive();
710 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
711 break;
712 case done:
713 return;
714 default:
715 // Ignore other actions intended for t2.
716 break;
721 std::thread t2{[&]() {
722 for (;;) {
723 switch (nextAction) {
724 case t2Starting:
725 AdvanceAction();
726 break;
727 case t2LockExclusiveAndBlock:
728 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
729 // Advance action before attempting to lock after t1's exclusive lock.
730 AdvanceAction();
731 sm.LockExclusive();
732 // We will only acquire the lock after t1 unlocks.
733 MOZ_RELEASE_ASSERT(nextAction == t2UnblockedAfterT1Unlock);
734 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
735 AdvanceAction();
736 break;
737 case t2UnlockExclusive:
738 MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
739 // Advance first, before unlocking, so that t1 sees the new state.
740 AdvanceAction();
741 sm.UnlockExclusive();
742 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
743 break;
744 case t2LockShared:
745 sm.LockShared();
746 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
747 AdvanceAction();
748 break;
749 case t2StillLockedShared:
750 AdvanceAction();
751 break;
752 case t2UnlockShared:
753 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
754 // Advance first, before unlocking, so that t1 sees the new state.
755 AdvanceAction();
756 sm.UnlockShared();
757 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
758 break;
759 case t2CheckAfterT1Lock:
760 MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
761 AdvanceAction();
762 break;
763 case done:
764 return;
765 default:
766 // Ignore other actions intended for t1.
767 break;
772 t1.join();
773 t2.join();
775 printf("TestSharedMutex done\n");
778 void TestProportionValue() {
779 printf("TestProportionValue...\n");
781 using mozilla::ProportionValue;
783 #define STATIC_ASSERT_EQ(a, b) \
784 static_assert((a) == (b)); \
785 MOZ_RELEASE_ASSERT((a) == (b));
787 #define STATIC_ASSERT(e) STATIC_ASSERT_EQ(e, true)
789 // Conversion from&to double.
790 STATIC_ASSERT_EQ(ProportionValue().ToDouble(), 0.0);
791 STATIC_ASSERT_EQ(ProportionValue(0.0).ToDouble(), 0.0);
792 STATIC_ASSERT_EQ(ProportionValue(0.5).ToDouble(), 0.5);
793 STATIC_ASSERT_EQ(ProportionValue(1.0).ToDouble(), 1.0);
795 // Clamping.
796 STATIC_ASSERT_EQ(
797 ProportionValue(std::numeric_limits<double>::min()).ToDouble(), 0.0);
798 STATIC_ASSERT_EQ(
799 ProportionValue(std::numeric_limits<long double>::min()).ToDouble(), 0.0);
800 STATIC_ASSERT_EQ(ProportionValue(-1.0).ToDouble(), 0.0);
801 STATIC_ASSERT_EQ(ProportionValue(-0.01).ToDouble(), 0.0);
802 STATIC_ASSERT_EQ(ProportionValue(-0.0).ToDouble(), 0.0);
803 STATIC_ASSERT_EQ(ProportionValue(1.01).ToDouble(), 1.0);
804 STATIC_ASSERT_EQ(
805 ProportionValue(std::numeric_limits<double>::max()).ToDouble(), 1.0);
807 // User-defined literal.
809 using namespace mozilla::literals::ProportionValue_literals;
810 STATIC_ASSERT_EQ(0_pc, ProportionValue(0.0));
811 STATIC_ASSERT_EQ(0._pc, ProportionValue(0.0));
812 STATIC_ASSERT_EQ(50_pc, ProportionValue(0.5));
813 STATIC_ASSERT_EQ(50._pc, ProportionValue(0.5));
814 STATIC_ASSERT_EQ(100_pc, ProportionValue(1.0));
815 STATIC_ASSERT_EQ(100._pc, ProportionValue(1.0));
816 STATIC_ASSERT_EQ(101_pc, ProportionValue(1.0));
817 STATIC_ASSERT_EQ(100.01_pc, ProportionValue(1.0));
818 STATIC_ASSERT_EQ(1000_pc, ProportionValue(1.0));
819 STATIC_ASSERT_EQ(1000._pc, ProportionValue(1.0));
822 // ProportionValue_literals is an inline namespace of mozilla::literals, so
823 // it's optional.
824 using namespace mozilla::literals;
825 STATIC_ASSERT_EQ(0_pc, ProportionValue(0.0));
826 STATIC_ASSERT_EQ(0._pc, ProportionValue(0.0));
827 STATIC_ASSERT_EQ(50_pc, ProportionValue(0.5));
828 STATIC_ASSERT_EQ(50._pc, ProportionValue(0.5));
829 STATIC_ASSERT_EQ(100_pc, ProportionValue(1.0));
830 STATIC_ASSERT_EQ(100._pc, ProportionValue(1.0));
831 STATIC_ASSERT_EQ(101_pc, ProportionValue(1.0));
832 STATIC_ASSERT_EQ(100.01_pc, ProportionValue(1.0));
833 STATIC_ASSERT_EQ(1000_pc, ProportionValue(1.0));
834 STATIC_ASSERT_EQ(1000._pc, ProportionValue(1.0));
837 // Invalid construction, conversion to double NaN.
838 MOZ_RELEASE_ASSERT(std::isnan(ProportionValue::MakeInvalid().ToDouble()));
840 using namespace mozilla::literals::ProportionValue_literals;
842 // Conversion to&from underlying integral number.
843 STATIC_ASSERT_EQ(
844 ProportionValue::FromUnderlyingType((0_pc).ToUnderlyingType()).ToDouble(),
845 0.0);
846 STATIC_ASSERT_EQ(
847 ProportionValue::FromUnderlyingType((50_pc).ToUnderlyingType())
848 .ToDouble(),
849 0.5);
850 STATIC_ASSERT_EQ(
851 ProportionValue::FromUnderlyingType((100_pc).ToUnderlyingType())
852 .ToDouble(),
853 1.0);
854 STATIC_ASSERT(ProportionValue::FromUnderlyingType(
855 ProportionValue::MakeInvalid().ToUnderlyingType())
856 .IsInvalid());
858 // IsExactlyZero.
859 STATIC_ASSERT(ProportionValue().IsExactlyZero());
860 STATIC_ASSERT((0_pc).IsExactlyZero());
861 STATIC_ASSERT(!(50_pc).IsExactlyZero());
862 STATIC_ASSERT(!(100_pc).IsExactlyZero());
863 STATIC_ASSERT(!ProportionValue::MakeInvalid().IsExactlyZero());
865 // IsExactlyOne.
866 STATIC_ASSERT(!ProportionValue().IsExactlyOne());
867 STATIC_ASSERT(!(0_pc).IsExactlyOne());
868 STATIC_ASSERT(!(50_pc).IsExactlyOne());
869 STATIC_ASSERT((100_pc).IsExactlyOne());
870 STATIC_ASSERT(!ProportionValue::MakeInvalid().IsExactlyOne());
872 // IsValid.
873 STATIC_ASSERT(ProportionValue().IsValid());
874 STATIC_ASSERT((0_pc).IsValid());
875 STATIC_ASSERT((50_pc).IsValid());
876 STATIC_ASSERT((100_pc).IsValid());
877 STATIC_ASSERT(!ProportionValue::MakeInvalid().IsValid());
879 // IsInvalid.
880 STATIC_ASSERT(!ProportionValue().IsInvalid());
881 STATIC_ASSERT(!(0_pc).IsInvalid());
882 STATIC_ASSERT(!(50_pc).IsInvalid());
883 STATIC_ASSERT(!(100_pc).IsInvalid());
884 STATIC_ASSERT(ProportionValue::MakeInvalid().IsInvalid());
886 // Addition.
887 STATIC_ASSERT_EQ((0_pc + 0_pc).ToDouble(), 0.0);
888 STATIC_ASSERT_EQ((0_pc + 100_pc).ToDouble(), 1.0);
889 STATIC_ASSERT_EQ((100_pc + 0_pc).ToDouble(), 1.0);
890 STATIC_ASSERT_EQ((100_pc + 100_pc).ToDouble(), 1.0);
891 STATIC_ASSERT((ProportionValue::MakeInvalid() + 50_pc).IsInvalid());
892 STATIC_ASSERT((50_pc + ProportionValue::MakeInvalid()).IsInvalid());
894 // Subtraction.
895 STATIC_ASSERT_EQ((0_pc - 0_pc).ToDouble(), 0.0);
896 STATIC_ASSERT_EQ((0_pc - 100_pc).ToDouble(), 0.0);
897 STATIC_ASSERT_EQ((100_pc - 0_pc).ToDouble(), 1.0);
898 STATIC_ASSERT_EQ((100_pc - 100_pc).ToDouble(), 0.0);
899 STATIC_ASSERT((ProportionValue::MakeInvalid() - 50_pc).IsInvalid());
900 STATIC_ASSERT((50_pc - ProportionValue::MakeInvalid()).IsInvalid());
902 // Multiplication.
903 STATIC_ASSERT_EQ((0_pc * 0_pc).ToDouble(), 0.0);
904 STATIC_ASSERT_EQ((0_pc * 100_pc).ToDouble(), 0.0);
905 STATIC_ASSERT_EQ((50_pc * 50_pc).ToDouble(), 0.25);
906 STATIC_ASSERT_EQ((50_pc * 100_pc).ToDouble(), 0.5);
907 STATIC_ASSERT_EQ((100_pc * 50_pc).ToDouble(), 0.5);
908 STATIC_ASSERT_EQ((100_pc * 0_pc).ToDouble(), 0.0);
909 STATIC_ASSERT_EQ((100_pc * 100_pc).ToDouble(), 1.0);
910 STATIC_ASSERT((ProportionValue::MakeInvalid() * 50_pc).IsInvalid());
911 STATIC_ASSERT((50_pc * ProportionValue::MakeInvalid()).IsInvalid());
913 // Division by a positive integer value.
914 STATIC_ASSERT_EQ((100_pc / 1u).ToDouble(), 1.0);
915 STATIC_ASSERT_EQ((100_pc / 2u).ToDouble(), 0.5);
916 STATIC_ASSERT_EQ(
917 (ProportionValue::FromUnderlyingType(6u) / 2u).ToUnderlyingType(), 3u);
918 STATIC_ASSERT_EQ(
919 (ProportionValue::FromUnderlyingType(5u) / 2u).ToUnderlyingType(), 2u);
920 STATIC_ASSERT_EQ(
921 (ProportionValue::FromUnderlyingType(1u) / 2u).ToUnderlyingType(), 0u);
922 STATIC_ASSERT_EQ(
923 (ProportionValue::FromUnderlyingType(0u) / 2u).ToUnderlyingType(), 0u);
924 STATIC_ASSERT((100_pc / 0u).IsInvalid());
925 STATIC_ASSERT((ProportionValue::MakeInvalid() / 2u).IsInvalid());
927 // Multiplication by a positive integer value.
928 STATIC_ASSERT_EQ((100_pc * 1u).ToDouble(), 1.0);
929 STATIC_ASSERT_EQ((50_pc * 1u).ToDouble(), 0.5);
930 STATIC_ASSERT_EQ((50_pc * 2u).ToDouble(), 1.0);
931 STATIC_ASSERT_EQ((50_pc * 3u).ToDouble(), 1.0); // Clamped.
932 STATIC_ASSERT_EQ(
933 (ProportionValue::FromUnderlyingType(1u) * 2u).ToUnderlyingType(), 2u);
934 STATIC_ASSERT((ProportionValue::MakeInvalid() * 2u).IsInvalid());
936 // Verifying PV - u < (PV / u) * u <= PV, with n=3, PV between 6 and 9 :
937 STATIC_ASSERT_EQ(
938 (ProportionValue::FromUnderlyingType(6u) / 3u).ToUnderlyingType(), 2u);
939 STATIC_ASSERT_EQ(
940 (ProportionValue::FromUnderlyingType(7u) / 3u).ToUnderlyingType(), 2u);
941 STATIC_ASSERT_EQ(
942 (ProportionValue::FromUnderlyingType(8u) / 3u).ToUnderlyingType(), 2u);
943 STATIC_ASSERT_EQ(
944 (ProportionValue::FromUnderlyingType(9u) / 3u).ToUnderlyingType(), 3u);
946 // Direct comparisons.
947 STATIC_ASSERT_EQ(0_pc, 0_pc);
948 STATIC_ASSERT(0_pc == 0_pc);
949 STATIC_ASSERT(!(0_pc == 100_pc));
950 STATIC_ASSERT(0_pc != 100_pc);
951 STATIC_ASSERT(!(0_pc != 0_pc));
952 STATIC_ASSERT(0_pc < 100_pc);
953 STATIC_ASSERT(!(0_pc < 0_pc));
954 STATIC_ASSERT(0_pc <= 0_pc);
955 STATIC_ASSERT(0_pc <= 100_pc);
956 STATIC_ASSERT(!(100_pc <= 0_pc));
957 STATIC_ASSERT(100_pc > 0_pc);
958 STATIC_ASSERT(!(100_pc > 100_pc));
959 STATIC_ASSERT(100_pc >= 0_pc);
960 STATIC_ASSERT(100_pc >= 100_pc);
961 STATIC_ASSERT(!(0_pc >= 100_pc));
962 // 0.5 is binary-friendly, so we can double it and compare it exactly.
963 STATIC_ASSERT_EQ(50_pc + 50_pc, 100_pc);
965 #undef STATIC_ASSERT_EQ
967 printf("TestProportionValue done\n");
970 template <typename Arg0, typename... Args>
971 bool AreAllEqual(Arg0&& aArg0, Args&&... aArgs) {
972 return ((aArg0 == aArgs) && ...);
975 void TestProgressLogger() {
976 printf("TestProgressLogger...\n");
978 using mozilla::ProgressLogger;
979 using mozilla::ProportionValue;
980 using namespace mozilla::literals::ProportionValue_literals;
982 auto progressRefPtr = mozilla::MakeRefPtr<ProgressLogger::SharedProgress>();
983 MOZ_RELEASE_ASSERT(progressRefPtr);
984 MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero());
987 ProgressLogger pl(progressRefPtr, "Started", "All done");
988 MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero());
989 MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyZero());
990 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
991 pl.GetLastGlobalLocation(), "Started"));
993 // At this top level, the scale is 1:1.
994 pl.SetLocalProgress(10_pc, "Top 10%");
995 MOZ_RELEASE_ASSERT(
996 AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(), 10_pc));
997 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
998 pl.GetLastGlobalLocation(), "Top 10%"));
1000 pl.SetLocalProgress(0_pc, "Restarted");
1001 MOZ_RELEASE_ASSERT(
1002 AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(), 0_pc));
1003 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
1004 pl.GetLastGlobalLocation(), "Restarted"));
1007 // Create a sub-logger for the whole global range. Notice that this is
1008 // moving the current progress back to 0.
1009 ProgressLogger plSub1 =
1010 pl.CreateSubLoggerFromTo(0_pc, "Sub1 started", 100_pc, "Sub1 ended");
1011 MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero());
1012 MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyZero());
1013 MOZ_RELEASE_ASSERT(plSub1.GetGlobalProgress().IsExactlyZero());
1014 MOZ_RELEASE_ASSERT(AreAllEqual(
1015 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
1016 plSub1.GetLastGlobalLocation(), "Sub1 started"));
1018 // At this level, the scale is still 1:1.
1019 plSub1.SetLocalProgress(10_pc, "Sub1 10%");
1020 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(),
1021 pl.GetGlobalProgress(),
1022 plSub1.GetGlobalProgress(), 10_pc));
1023 MOZ_RELEASE_ASSERT(AreAllEqual(
1024 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
1025 plSub1.GetLastGlobalLocation(), "Sub1 10%"));
1028 // Create a sub-logger half the global range.
1029 // 0 0.25 0.375 0.5 0.625 0.75 1
1030 // |---------------|-------|-------|-------|-------|---------------|
1031 // plSub2: 0 0.25 0.5 0.75 1
1032 ProgressLogger plSub2 = plSub1.CreateSubLoggerFromTo(
1033 25_pc, "Sub2 started", 75_pc, "Sub2 ended");
1034 MOZ_RELEASE_ASSERT(AreAllEqual(
1035 progressRefPtr->Progress(), pl.GetGlobalProgress(),
1036 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 25_pc));
1037 MOZ_RELEASE_ASSERT(AreAllEqual(
1038 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
1039 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
1040 "Sub2 started"));
1042 plSub2.SetLocalProgress(25_pc, "Sub2 25%");
1043 MOZ_RELEASE_ASSERT(AreAllEqual(
1044 progressRefPtr->Progress(), pl.GetGlobalProgress(),
1045 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 37.5_pc));
1046 MOZ_RELEASE_ASSERT(AreAllEqual(
1047 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
1048 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
1049 "Sub2 25%"));
1051 plSub2.SetLocalProgress(50_pc, "Sub2 50%");
1052 MOZ_RELEASE_ASSERT(AreAllEqual(
1053 progressRefPtr->Progress(), pl.GetGlobalProgress(),
1054 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 50_pc));
1055 MOZ_RELEASE_ASSERT(AreAllEqual(
1056 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
1057 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
1058 "Sub2 50%"));
1061 // Create a sub-logger half the parent range.
1062 // 0 0.25 0.375 0.5 0.625 0.75 1
1063 // |---------------|-------|-------|-------|-------|---------------|
1064 // plSub2: 0 0.25 0.5 0.75 1
1065 // plSub3: 0 0.5 1
1066 ProgressLogger plSub3 = plSub2.CreateSubLoggerTo(
1067 "Sub3 started", 100_pc, ProgressLogger::NO_LOCATION_UPDATE);
1068 MOZ_RELEASE_ASSERT(AreAllEqual(
1069 progressRefPtr->Progress(), pl.GetGlobalProgress(),
1070 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(),
1071 plSub3.GetGlobalProgress(), 50_pc));
1072 MOZ_RELEASE_ASSERT(AreAllEqual(
1073 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
1074 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
1075 plSub3.GetLastGlobalLocation(), "Sub3 started"));
1077 plSub3.SetLocalProgress(50_pc, "Sub3 50%");
1078 MOZ_RELEASE_ASSERT(AreAllEqual(
1079 progressRefPtr->Progress(), pl.GetGlobalProgress(),
1080 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(),
1081 plSub3.GetGlobalProgress(), 62.5_pc));
1082 MOZ_RELEASE_ASSERT(AreAllEqual(
1083 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
1084 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
1085 plSub3.GetLastGlobalLocation(), "Sub3 50%"));
1086 } // End of plSub3
1088 // When plSub3 ends, progress moves to its 100%, which is also plSub2's
1089 // 100%, which is plSub1's and the global progress of 75%
1090 MOZ_RELEASE_ASSERT(AreAllEqual(
1091 progressRefPtr->Progress(), pl.GetGlobalProgress(),
1092 plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 75_pc));
1093 // But location is still at the last explicit update.
1094 MOZ_RELEASE_ASSERT(AreAllEqual(
1095 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
1096 plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
1097 "Sub3 50%"));
1098 } // End of plSub2
1100 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(),
1101 pl.GetGlobalProgress(),
1102 plSub1.GetGlobalProgress(), 75_pc));
1103 MOZ_RELEASE_ASSERT(AreAllEqual(
1104 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
1105 plSub1.GetLastGlobalLocation(), "Sub2 ended"));
1106 } // End of plSub1
1108 MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyOne());
1109 MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyOne());
1110 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
1111 pl.GetLastGlobalLocation(), "Sub1 ended"));
1113 const auto loopStart = 75_pc;
1114 const auto loopEnd = 87.5_pc;
1115 const uint32_t loopCount = 8;
1116 uint32_t expectedIndex = 0u;
1117 auto expectedIterationStart = loopStart;
1118 const auto iterationIncrement = (loopEnd - loopStart) / loopCount;
1119 for (auto&& [index, loopPL] : pl.CreateLoopSubLoggersFromTo(
1120 loopStart, loopEnd, loopCount, "looping...")) {
1121 MOZ_RELEASE_ASSERT(index == expectedIndex);
1122 ++expectedIndex;
1123 MOZ_RELEASE_ASSERT(
1124 AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(),
1125 loopPL.GetGlobalProgress(), expectedIterationStart));
1126 MOZ_RELEASE_ASSERT(AreAllEqual(
1127 progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
1128 loopPL.GetLastGlobalLocation(), "looping..."));
1130 loopPL.SetLocalProgress(50_pc, "half");
1131 MOZ_RELEASE_ASSERT(loopPL.GetGlobalProgress() ==
1132 expectedIterationStart + iterationIncrement / 2u);
1133 MOZ_RELEASE_ASSERT(
1134 AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(),
1135 loopPL.GetGlobalProgress(),
1136 expectedIterationStart + iterationIncrement / 2u));
1137 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
1138 pl.GetLastGlobalLocation(),
1139 loopPL.GetLastGlobalLocation(), "half"));
1141 expectedIterationStart = expectedIterationStart + iterationIncrement;
1143 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(),
1144 pl.GetGlobalProgress(),
1145 expectedIterationStart));
1146 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
1147 pl.GetLastGlobalLocation(), "looping..."));
1148 } // End of pl
1149 MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyOne());
1150 MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(), "All done"));
1152 printf("TestProgressLogger done\n");
1155 #ifdef MOZ_GECKO_PROFILER
1157 MOZ_MAYBE_UNUSED static void SleepMilli(unsigned aMilliseconds) {
1158 # if defined(_MSC_VER) || defined(__MINGW32__)
1159 Sleep(aMilliseconds);
1160 # else
1161 struct timespec ts = {/* .tv_sec */ static_cast<time_t>(aMilliseconds / 1000),
1162 /* ts.tv_nsec */ long(aMilliseconds % 1000) * 1000000};
1163 struct timespec tr = {0, 0};
1164 while (nanosleep(&ts, &tr)) {
1165 if (errno == EINTR) {
1166 ts = tr;
1167 } else {
1168 printf("nanosleep() -> %s\n", strerror(errno));
1169 exit(1);
1172 # endif
1175 MOZ_MAYBE_UNUSED static void WaitUntilTimeStampChanges(
1176 const mozilla::TimeStamp& aTimeStampToCompare = mozilla::TimeStamp::Now()) {
1177 while (aTimeStampToCompare == mozilla::TimeStamp::Now()) {
1178 SleepMilli(1);
1182 using namespace mozilla;
1184 void TestPowerOfTwoMask() {
1185 printf("TestPowerOfTwoMask...\n");
1187 static_assert(MakePowerOfTwoMask<uint32_t, 0>().MaskValue() == 0);
1188 constexpr PowerOfTwoMask<uint32_t> c0 = MakePowerOfTwoMask<uint32_t, 0>();
1189 MOZ_RELEASE_ASSERT(c0.MaskValue() == 0);
1191 static_assert(MakePowerOfTwoMask<uint32_t, 0xFFu>().MaskValue() == 0xFFu);
1192 constexpr PowerOfTwoMask<uint32_t> cFF =
1193 MakePowerOfTwoMask<uint32_t, 0xFFu>();
1194 MOZ_RELEASE_ASSERT(cFF.MaskValue() == 0xFFu);
1196 static_assert(MakePowerOfTwoMask<uint32_t, 0xFFFFFFFFu>().MaskValue() ==
1197 0xFFFFFFFFu);
1198 constexpr PowerOfTwoMask<uint32_t> cFFFFFFFF =
1199 MakePowerOfTwoMask<uint32_t, 0xFFFFFFFFu>();
1200 MOZ_RELEASE_ASSERT(cFFFFFFFF.MaskValue() == 0xFFFFFFFFu);
1202 struct TestDataU32 {
1203 uint32_t mInput;
1204 uint32_t mMask;
1206 // clang-format off
1207 TestDataU32 tests[] = {
1208 { 0, 0 },
1209 { 1, 1 },
1210 { 2, 3 },
1211 { 3, 3 },
1212 { 4, 7 },
1213 { 5, 7 },
1214 { (1u << 31) - 1, (1u << 31) - 1 },
1215 { (1u << 31), uint32_t(-1) },
1216 { (1u << 31) + 1, uint32_t(-1) },
1217 { uint32_t(-1), uint32_t(-1) }
1219 // clang-format on
1220 for (const TestDataU32& test : tests) {
1221 PowerOfTwoMask<uint32_t> p2m(test.mInput);
1222 MOZ_RELEASE_ASSERT(p2m.MaskValue() == test.mMask);
1223 for (const TestDataU32& inner : tests) {
1224 if (p2m.MaskValue() != uint32_t(-1)) {
1225 MOZ_RELEASE_ASSERT((inner.mInput % p2m) ==
1226 (inner.mInput % (p2m.MaskValue() + 1)));
1228 MOZ_RELEASE_ASSERT((inner.mInput & p2m) == (inner.mInput % p2m));
1229 MOZ_RELEASE_ASSERT((p2m & inner.mInput) == (inner.mInput & p2m));
1233 printf("TestPowerOfTwoMask done\n");
1236 void TestPowerOfTwo() {
1237 printf("TestPowerOfTwo...\n");
1239 static_assert(MakePowerOfTwo<uint32_t, 1>().Value() == 1);
1240 constexpr PowerOfTwo<uint32_t> c1 = MakePowerOfTwo<uint32_t, 1>();
1241 MOZ_RELEASE_ASSERT(c1.Value() == 1);
1242 static_assert(MakePowerOfTwo<uint32_t, 1>().Mask().MaskValue() == 0);
1244 static_assert(MakePowerOfTwo<uint32_t, 128>().Value() == 128);
1245 constexpr PowerOfTwo<uint32_t> c128 = MakePowerOfTwo<uint32_t, 128>();
1246 MOZ_RELEASE_ASSERT(c128.Value() == 128);
1247 static_assert(MakePowerOfTwo<uint32_t, 128>().Mask().MaskValue() == 127);
1249 static_assert(MakePowerOfTwo<uint32_t, 0x80000000u>().Value() == 0x80000000u);
1250 constexpr PowerOfTwo<uint32_t> cMax = MakePowerOfTwo<uint32_t, 0x80000000u>();
1251 MOZ_RELEASE_ASSERT(cMax.Value() == 0x80000000u);
1252 static_assert(MakePowerOfTwo<uint32_t, 0x80000000u>().Mask().MaskValue() ==
1253 0x7FFFFFFFu);
1255 struct TestDataU32 {
1256 uint32_t mInput;
1257 uint32_t mValue;
1258 uint32_t mMask;
1260 // clang-format off
1261 TestDataU32 tests[] = {
1262 { 0, 1, 0 },
1263 { 1, 1, 0 },
1264 { 2, 2, 1 },
1265 { 3, 4, 3 },
1266 { 4, 4, 3 },
1267 { 5, 8, 7 },
1268 { (1u << 31) - 1, (1u << 31), (1u << 31) - 1 },
1269 { (1u << 31), (1u << 31), (1u << 31) - 1 },
1270 { (1u << 31) + 1, (1u << 31), (1u << 31) - 1 },
1271 { uint32_t(-1), (1u << 31), (1u << 31) - 1 }
1273 // clang-format on
1274 for (const TestDataU32& test : tests) {
1275 PowerOfTwo<uint32_t> p2(test.mInput);
1276 MOZ_RELEASE_ASSERT(p2.Value() == test.mValue);
1277 MOZ_RELEASE_ASSERT(p2.MaskValue() == test.mMask);
1278 PowerOfTwoMask<uint32_t> p2m = p2.Mask();
1279 MOZ_RELEASE_ASSERT(p2m.MaskValue() == test.mMask);
1280 for (const TestDataU32& inner : tests) {
1281 MOZ_RELEASE_ASSERT((inner.mInput % p2) == (inner.mInput % p2.Value()));
1285 printf("TestPowerOfTwo done\n");
1288 void TestLEB128() {
1289 printf("TestLEB128...\n");
1291 MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint8_t>() == 2);
1292 MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint16_t>() == 3);
1293 MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint32_t>() == 5);
1294 MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint64_t>() == 10);
1296 struct TestDataU64 {
1297 uint64_t mValue;
1298 unsigned mSize;
1299 const char* mBytes;
1301 // clang-format off
1302 TestDataU64 tests[] = {
1303 // Small numbers should keep their normal byte representation.
1304 { 0u, 1, "\0" },
1305 { 1u, 1, "\x01" },
1307 // 0111 1111 (127, or 0x7F) is the highest number that fits into a single
1308 // LEB128 byte. It gets encoded as 0111 1111, note the most significant bit
1309 // is off.
1310 { 0x7Fu, 1, "\x7F" },
1312 // Next number: 128, or 0x80.
1313 // Original data representation: 1000 0000
1314 // Broken up into groups of 7: 1 0000000
1315 // Padded with 0 (msB) or 1 (lsB): 00000001 10000000
1316 // Byte representation: 0x01 0x80
1317 // Little endian order: -> 0x80 0x01
1318 { 0x80u, 2, "\x80\x01" },
1320 // Next: 129, or 0x81 (showing that we don't lose low bits.)
1321 // Original data representation: 1000 0001
1322 // Broken up into groups of 7: 1 0000001
1323 // Padded with 0 (msB) or 1 (lsB): 00000001 10000001
1324 // Byte representation: 0x01 0x81
1325 // Little endian order: -> 0x81 0x01
1326 { 0x81u, 2, "\x81\x01" },
1328 // Highest 8-bit number: 255, or 0xFF.
1329 // Original data representation: 1111 1111
1330 // Broken up into groups of 7: 1 1111111
1331 // Padded with 0 (msB) or 1 (lsB): 00000001 11111111
1332 // Byte representation: 0x01 0xFF
1333 // Little endian order: -> 0xFF 0x01
1334 { 0xFFu, 2, "\xFF\x01" },
1336 // Next: 256, or 0x100.
1337 // Original data representation: 1 0000 0000
1338 // Broken up into groups of 7: 10 0000000
1339 // Padded with 0 (msB) or 1 (lsB): 00000010 10000000
1340 // Byte representation: 0x10 0x80
1341 // Little endian order: -> 0x80 0x02
1342 { 0x100u, 2, "\x80\x02" },
1344 // Highest 32-bit number: 0xFFFFFFFF (8 bytes, all bits set).
1345 // Original: 1111 1111 1111 1111 1111 1111 1111 1111
1346 // Groups: 1111 1111111 1111111 1111111 1111111
1347 // Padded: 00001111 11111111 11111111 11111111 11111111
1348 // Bytes: 0x0F 0xFF 0xFF 0xFF 0xFF
1349 // Little Endian: -> 0xFF 0xFF 0xFF 0xFF 0x0F
1350 { 0xFFFFFFFFu, 5, "\xFF\xFF\xFF\xFF\x0F" },
1352 // Highest 64-bit number: 0xFFFFFFFFFFFFFFFF (16 bytes, all bits set).
1353 // 64 bits, that's 9 groups of 7 bits, plus 1 (most significant) bit.
1354 { 0xFFFFFFFFFFFFFFFFu, 10, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01" }
1356 // clang-format on
1358 for (const TestDataU64& test : tests) {
1359 MOZ_RELEASE_ASSERT(ULEB128Size(test.mValue) == test.mSize);
1360 // Prepare a buffer that can accomodate the largest-possible LEB128.
1361 uint8_t buffer[ULEB128MaxSize<uint64_t>()];
1362 // Use a pointer into the buffer as iterator.
1363 uint8_t* p = buffer;
1364 // And write the LEB128.
1365 WriteULEB128(test.mValue, p);
1366 // Pointer (iterator) should have advanced just past the expected LEB128
1367 // size.
1368 MOZ_RELEASE_ASSERT(p == buffer + test.mSize);
1369 // Check expected bytes.
1370 for (unsigned i = 0; i < test.mSize; ++i) {
1371 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t(test.mBytes[i]));
1374 // Move pointer (iterator) back to start of buffer.
1375 p = buffer;
1376 // And read the LEB128 we wrote above.
1377 uint64_t read = ReadULEB128<uint64_t>(p);
1378 // Pointer (iterator) should have also advanced just past the expected
1379 // LEB128 size.
1380 MOZ_RELEASE_ASSERT(p == buffer + test.mSize);
1381 // And check the read value.
1382 MOZ_RELEASE_ASSERT(read == test.mValue);
1384 // Testing ULEB128 reader.
1385 ULEB128Reader<uint64_t> reader;
1386 MOZ_RELEASE_ASSERT(!reader.IsComplete());
1387 // Move pointer back to start of buffer.
1388 p = buffer;
1389 for (;;) {
1390 // Read a byte and feed it to the reader.
1391 if (reader.FeedByteIsComplete(*p++)) {
1392 break;
1394 // Not complete yet, we shouldn't have reached the end pointer.
1395 MOZ_RELEASE_ASSERT(!reader.IsComplete());
1396 MOZ_RELEASE_ASSERT(p < buffer + test.mSize);
1398 MOZ_RELEASE_ASSERT(reader.IsComplete());
1399 // Pointer should have advanced just past the expected LEB128 size.
1400 MOZ_RELEASE_ASSERT(p == buffer + test.mSize);
1401 // And check the read value.
1402 MOZ_RELEASE_ASSERT(reader.Value() == test.mValue);
1404 // And again after a Reset.
1405 reader.Reset();
1406 MOZ_RELEASE_ASSERT(!reader.IsComplete());
1407 p = buffer;
1408 for (;;) {
1409 if (reader.FeedByteIsComplete(*p++)) {
1410 break;
1412 MOZ_RELEASE_ASSERT(!reader.IsComplete());
1413 MOZ_RELEASE_ASSERT(p < buffer + test.mSize);
1415 MOZ_RELEASE_ASSERT(reader.IsComplete());
1416 MOZ_RELEASE_ASSERT(p == buffer + test.mSize);
1417 MOZ_RELEASE_ASSERT(reader.Value() == test.mValue);
1420 printf("TestLEB128 done\n");
1423 struct StringWriteFunc final : public JSONWriteFunc {
1424 std::string mString;
1426 void Write(const mozilla::Span<const char>& aStr) final {
1427 mString.append(aStr.data(), aStr.size());
1431 void CheckJSON(mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
1432 const char* aExpected, int aLine) {
1433 const std::string& actual =
1434 static_cast<StringWriteFunc&>(aWriter.WriteFunc()).mString;
1435 if (strcmp(aExpected, actual.c_str()) != 0) {
1436 fprintf(stderr,
1437 "---- EXPECTED ---- (line %d)\n<<<%s>>>\n"
1438 "---- ACTUAL ----\n<<<%s>>>\n",
1439 aLine, aExpected, actual.c_str());
1440 MOZ_RELEASE_ASSERT(false, "expected and actual output don't match");
1444 void TestJSONTimeOutput() {
1445 printf("TestJSONTimeOutput...\n");
1447 # define TEST(in, out) \
1448 do { \
1449 mozilla::baseprofiler::SpliceableJSONWriter writer( \
1450 mozilla::MakeUnique<StringWriteFunc>(), \
1451 FailureLatchInfallibleSource::Singleton()); \
1452 writer.Start(); \
1453 writer.TimeDoubleMsProperty("time_ms", (in)); \
1454 writer.End(); \
1455 CheckJSON(writer, "{\"time_ms\":" out "}", __LINE__); \
1456 } while (false);
1458 TEST(0, "0");
1460 TEST(0.000'000'1, "0");
1461 TEST(0.000'000'4, "0");
1462 TEST(0.000'000'499, "0");
1463 TEST(0.000'000'5, "0.000001");
1464 TEST(0.000'001, "0.000001");
1465 TEST(0.000'01, "0.00001");
1466 TEST(0.000'1, "0.0001");
1467 TEST(0.001, "0.001");
1468 TEST(0.01, "0.01");
1469 TEST(0.1, "0.1");
1470 TEST(1, "1");
1471 TEST(2, "2");
1472 TEST(10, "10");
1473 TEST(100, "100");
1474 TEST(1'000, "1000");
1475 TEST(10'000, "10000");
1476 TEST(100'000, "100000");
1477 TEST(1'000'000, "1000000");
1478 // 2^53-2 ns in ms. 2^53-1 is the highest integer value representable in
1479 // double, -1 again because we're adding 0.5 before truncating.
1480 // That's 104 days, after which the nanosecond precision would decrease.
1481 TEST(9'007'199'254.740'990, "9007199254.74099");
1483 TEST(-0.000'000'1, "0");
1484 TEST(-0.000'000'4, "0");
1485 TEST(-0.000'000'499, "0");
1486 TEST(-0.000'000'5, "-0.000001");
1487 TEST(-0.000'001, "-0.000001");
1488 TEST(-0.000'01, "-0.00001");
1489 TEST(-0.000'1, "-0.0001");
1490 TEST(-0.001, "-0.001");
1491 TEST(-0.01, "-0.01");
1492 TEST(-0.1, "-0.1");
1493 TEST(-1, "-1");
1494 TEST(-2, "-2");
1495 TEST(-10, "-10");
1496 TEST(-100, "-100");
1497 TEST(-1'000, "-1000");
1498 TEST(-10'000, "-10000");
1499 TEST(-100'000, "-100000");
1500 TEST(-1'000'000, "-1000000");
1501 TEST(-9'007'199'254.740'990, "-9007199254.74099");
1503 # undef TEST
1505 printf("TestJSONTimeOutput done\n");
1508 template <uint8_t byte, uint8_t... tail>
1509 constexpr bool TestConstexprULEB128Reader(ULEB128Reader<uint64_t>& aReader) {
1510 if (aReader.IsComplete()) {
1511 return false;
1513 const bool isComplete = aReader.FeedByteIsComplete(byte);
1514 if (aReader.IsComplete() != isComplete) {
1515 return false;
1517 if constexpr (sizeof...(tail) == 0) {
1518 return isComplete;
1519 } else {
1520 if (isComplete) {
1521 return false;
1523 return TestConstexprULEB128Reader<tail...>(aReader);
1527 template <uint64_t expected, uint8_t... bytes>
1528 constexpr bool TestConstexprULEB128Reader() {
1529 ULEB128Reader<uint64_t> reader;
1530 if (!TestConstexprULEB128Reader<bytes...>(reader)) {
1531 return false;
1533 if (!reader.IsComplete()) {
1534 return false;
1536 if (reader.Value() != expected) {
1537 return false;
1540 reader.Reset();
1541 if (!TestConstexprULEB128Reader<bytes...>(reader)) {
1542 return false;
1544 if (!reader.IsComplete()) {
1545 return false;
1547 if (reader.Value() != expected) {
1548 return false;
1551 return true;
1554 static_assert(TestConstexprULEB128Reader<0x0u, 0x0u>());
1555 static_assert(!TestConstexprULEB128Reader<0x0u, 0x0u, 0x0u>());
1556 static_assert(TestConstexprULEB128Reader<0x1u, 0x1u>());
1557 static_assert(TestConstexprULEB128Reader<0x7Fu, 0x7Fu>());
1558 static_assert(TestConstexprULEB128Reader<0x80u, 0x80u, 0x01u>());
1559 static_assert(!TestConstexprULEB128Reader<0x80u, 0x80u>());
1560 static_assert(!TestConstexprULEB128Reader<0x80u, 0x01u>());
1561 static_assert(TestConstexprULEB128Reader<0x81u, 0x81u, 0x01u>());
1562 static_assert(TestConstexprULEB128Reader<0xFFu, 0xFFu, 0x01u>());
1563 static_assert(TestConstexprULEB128Reader<0x100u, 0x80u, 0x02u>());
1564 static_assert(TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu,
1565 0xFFu, 0x0Fu>());
1566 static_assert(
1567 !TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu>());
1568 static_assert(!TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu,
1569 0xFFu, 0xFFu, 0x0Fu>());
1570 static_assert(
1571 TestConstexprULEB128Reader<0xFFFFFFFFFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
1572 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0x01u>());
1573 static_assert(
1574 !TestConstexprULEB128Reader<0xFFFFFFFFFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
1575 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu>());
1577 static void TestChunk() {
1578 printf("TestChunk...\n");
1580 static_assert(!std::is_default_constructible_v<ProfileBufferChunk>,
1581 "ProfileBufferChunk should not be default-constructible");
1582 static_assert(
1583 !std::is_constructible_v<ProfileBufferChunk, ProfileBufferChunk::Length>,
1584 "ProfileBufferChunk should not be constructible from Length");
1586 static_assert(
1587 sizeof(ProfileBufferChunk::Header) ==
1588 sizeof(ProfileBufferChunk::Header::mOffsetFirstBlock) +
1589 sizeof(ProfileBufferChunk::Header::mOffsetPastLastBlock) +
1590 sizeof(ProfileBufferChunk::Header::mStartTimeStamp) +
1591 sizeof(ProfileBufferChunk::Header::mDoneTimeStamp) +
1592 sizeof(ProfileBufferChunk::Header::mBufferBytes) +
1593 sizeof(ProfileBufferChunk::Header::mBlockCount) +
1594 sizeof(ProfileBufferChunk::Header::mRangeStart) +
1595 sizeof(ProfileBufferChunk::Header::mProcessId) +
1596 sizeof(ProfileBufferChunk::Header::mPADDING),
1597 "ProfileBufferChunk::Header may have unwanted padding, please review");
1598 // Note: The above static_assert is an attempt at keeping
1599 // ProfileBufferChunk::Header tightly packed, but some changes could make this
1600 // impossible to achieve (most probably due to alignment) -- Just do your
1601 // best!
1603 constexpr ProfileBufferChunk::Length TestLen = 1000;
1605 // Basic allocations of different sizes.
1606 for (ProfileBufferChunk::Length len = 0; len <= TestLen; ++len) {
1607 auto chunk = ProfileBufferChunk::Create(len);
1608 static_assert(
1609 std::is_same_v<decltype(chunk), UniquePtr<ProfileBufferChunk>>,
1610 "ProfileBufferChunk::Create() should return a "
1611 "UniquePtr<ProfileBufferChunk>");
1612 MOZ_RELEASE_ASSERT(!!chunk, "OOM!?");
1613 MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= len);
1614 MOZ_RELEASE_ASSERT(chunk->ChunkBytes() >=
1615 len + ProfileBufferChunk::SizeofChunkMetadata());
1616 MOZ_RELEASE_ASSERT(chunk->RemainingBytes() == chunk->BufferBytes());
1617 MOZ_RELEASE_ASSERT(chunk->OffsetFirstBlock() == 0);
1618 MOZ_RELEASE_ASSERT(chunk->OffsetPastLastBlock() == 0);
1619 MOZ_RELEASE_ASSERT(chunk->BlockCount() == 0);
1620 MOZ_RELEASE_ASSERT(chunk->ProcessId() == 0);
1621 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0);
1622 MOZ_RELEASE_ASSERT(chunk->BufferSpan().LengthBytes() ==
1623 chunk->BufferBytes());
1624 MOZ_RELEASE_ASSERT(!chunk->GetNext());
1625 MOZ_RELEASE_ASSERT(!chunk->ReleaseNext());
1626 MOZ_RELEASE_ASSERT(chunk->Last() == chunk.get());
1629 // Allocate the main test Chunk.
1630 auto chunkA = ProfileBufferChunk::Create(TestLen);
1631 MOZ_RELEASE_ASSERT(!!chunkA, "OOM!?");
1632 MOZ_RELEASE_ASSERT(chunkA->BufferBytes() >= TestLen);
1633 MOZ_RELEASE_ASSERT(chunkA->ChunkBytes() >=
1634 TestLen + ProfileBufferChunk::SizeofChunkMetadata());
1635 MOZ_RELEASE_ASSERT(!chunkA->GetNext());
1636 MOZ_RELEASE_ASSERT(!chunkA->ReleaseNext());
1638 constexpr ProfileBufferIndex chunkARangeStart = 12345;
1639 chunkA->SetRangeStart(chunkARangeStart);
1640 MOZ_RELEASE_ASSERT(chunkA->RangeStart() == chunkARangeStart);
1642 // Get a read-only span over its buffer.
1643 auto bufferA = chunkA->BufferSpan();
1644 static_assert(
1645 std::is_same_v<decltype(bufferA), Span<const ProfileBufferChunk::Byte>>,
1646 "BufferSpan() should return a Span<const Byte>");
1647 MOZ_RELEASE_ASSERT(bufferA.LengthBytes() == chunkA->BufferBytes());
1649 // Add the initial tail block.
1650 constexpr ProfileBufferChunk::Length initTailLen = 10;
1651 auto initTail = chunkA->ReserveInitialBlockAsTail(initTailLen);
1652 static_assert(
1653 std::is_same_v<decltype(initTail), Span<ProfileBufferChunk::Byte>>,
1654 "ReserveInitialBlockAsTail() should return a Span<Byte>");
1655 MOZ_RELEASE_ASSERT(initTail.LengthBytes() == initTailLen);
1656 MOZ_RELEASE_ASSERT(initTail.Elements() == bufferA.Elements());
1657 MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == initTailLen);
1658 MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == initTailLen);
1660 // Add the first complete block.
1661 constexpr ProfileBufferChunk::Length block1Len = 20;
1662 auto block1 = chunkA->ReserveBlock(block1Len);
1663 static_assert(
1664 std::is_same_v<decltype(block1), ProfileBufferChunk::ReserveReturn>,
1665 "ReserveBlock() should return a ReserveReturn");
1666 MOZ_RELEASE_ASSERT(block1.mBlockRangeIndex.ConvertToProfileBufferIndex() ==
1667 chunkARangeStart + initTailLen);
1668 MOZ_RELEASE_ASSERT(block1.mSpan.LengthBytes() == block1Len);
1669 MOZ_RELEASE_ASSERT(block1.mSpan.Elements() ==
1670 bufferA.Elements() + initTailLen);
1671 MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == initTailLen);
1672 MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == initTailLen + block1Len);
1673 MOZ_RELEASE_ASSERT(chunkA->RemainingBytes() != 0);
1675 // Add another block to over-fill the ProfileBufferChunk.
1676 const ProfileBufferChunk::Length remaining =
1677 chunkA->BufferBytes() - (initTailLen + block1Len);
1678 constexpr ProfileBufferChunk::Length overfill = 30;
1679 const ProfileBufferChunk::Length block2Len = remaining + overfill;
1680 ProfileBufferChunk::ReserveReturn block2 = chunkA->ReserveBlock(block2Len);
1681 MOZ_RELEASE_ASSERT(block2.mBlockRangeIndex.ConvertToProfileBufferIndex() ==
1682 chunkARangeStart + initTailLen + block1Len);
1683 MOZ_RELEASE_ASSERT(block2.mSpan.LengthBytes() == remaining);
1684 MOZ_RELEASE_ASSERT(block2.mSpan.Elements() ==
1685 bufferA.Elements() + initTailLen + block1Len);
1686 MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == initTailLen);
1687 MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == chunkA->BufferBytes());
1688 MOZ_RELEASE_ASSERT(chunkA->RemainingBytes() == 0);
1690 // Block must be marked "done" before it can be recycled.
1691 chunkA->MarkDone();
1693 // It must be marked "recycled" before data can be added to it again.
1694 chunkA->MarkRecycled();
1696 // Add an empty initial tail block.
1697 Span<ProfileBufferChunk::Byte> initTail2 =
1698 chunkA->ReserveInitialBlockAsTail(0);
1699 MOZ_RELEASE_ASSERT(initTail2.LengthBytes() == 0);
1700 MOZ_RELEASE_ASSERT(initTail2.Elements() == bufferA.Elements());
1701 MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == 0);
1702 MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == 0);
1704 // Block must be marked "done" before it can be destroyed.
1705 chunkA->MarkDone();
1707 chunkA->SetProcessId(123);
1708 MOZ_RELEASE_ASSERT(chunkA->ProcessId() == 123);
1710 printf("TestChunk done\n");
1713 static void TestChunkManagerSingle() {
1714 printf("TestChunkManagerSingle...\n");
1716 // Construct a ProfileBufferChunkManagerSingle for one chunk of size >=1000.
1717 constexpr ProfileBufferChunk::Length ChunkMinBufferBytes = 1000;
1718 ProfileBufferChunkManagerSingle cms{ChunkMinBufferBytes};
1720 // Reference to base class, to exercize virtual methods.
1721 ProfileBufferChunkManager& cm = cms;
1723 # ifdef DEBUG
1724 const char* chunkManagerRegisterer = "TestChunkManagerSingle";
1725 cm.RegisteredWith(chunkManagerRegisterer);
1726 # endif // DEBUG
1728 const auto maxTotalSize = cm.MaxTotalSize();
1729 MOZ_RELEASE_ASSERT(maxTotalSize >= ChunkMinBufferBytes);
1731 cm.SetChunkDestroyedCallback([](const ProfileBufferChunk&) {
1732 MOZ_RELEASE_ASSERT(
1733 false,
1734 "ProfileBufferChunkManagerSingle should never destroy its one chunk");
1737 UniquePtr<ProfileBufferChunk> extantReleasedChunks =
1738 cm.GetExtantReleasedChunks();
1739 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
1741 // First request.
1742 UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk();
1743 MOZ_RELEASE_ASSERT(!!chunk, "First chunk request should always work");
1744 MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= ChunkMinBufferBytes,
1745 "Unexpected chunk size");
1746 MOZ_RELEASE_ASSERT(!chunk->GetNext(), "There should only be one chunk");
1748 // Keep address, for later checks.
1749 const uintptr_t chunkAddress = reinterpret_cast<uintptr_t>(chunk.get());
1751 extantReleasedChunks = cm.GetExtantReleasedChunks();
1752 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
1754 // Second request.
1755 MOZ_RELEASE_ASSERT(!cm.GetChunk(), "Second chunk request should always fail");
1757 extantReleasedChunks = cm.GetExtantReleasedChunks();
1758 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
1760 // Add some data to the chunk (to verify recycling later on).
1761 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 0);
1762 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 0);
1763 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0);
1764 chunk->SetRangeStart(100);
1765 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 100);
1766 Unused << chunk->ReserveInitialBlockAsTail(1);
1767 Unused << chunk->ReserveBlock(2);
1768 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 1);
1769 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 1 + 2);
1771 // Release the first chunk.
1772 chunk->MarkDone();
1773 cm.ReleaseChunk(std::move(chunk));
1774 MOZ_RELEASE_ASSERT(!chunk, "chunk UniquePtr should have been moved-from");
1776 // Request after release.
1777 MOZ_RELEASE_ASSERT(!cm.GetChunk(),
1778 "Chunk request after release should also fail");
1780 // Check released chunk.
1781 extantReleasedChunks = cm.GetExtantReleasedChunks();
1782 MOZ_RELEASE_ASSERT(!!extantReleasedChunks,
1783 "Could not retrieve released chunk");
1784 MOZ_RELEASE_ASSERT(!extantReleasedChunks->GetNext(),
1785 "There should only be one released chunk");
1786 MOZ_RELEASE_ASSERT(
1787 reinterpret_cast<uintptr_t>(extantReleasedChunks.get()) == chunkAddress,
1788 "Released chunk should be first requested one");
1790 MOZ_RELEASE_ASSERT(!cm.GetExtantReleasedChunks(),
1791 "Unexpected extra released chunk(s)");
1793 // Another request after release.
1794 MOZ_RELEASE_ASSERT(!cm.GetChunk(),
1795 "Chunk request after release should also fail");
1797 MOZ_RELEASE_ASSERT(
1798 cm.MaxTotalSize() == maxTotalSize,
1799 "MaxTotalSize() should not change after requests&releases");
1801 // Reset the chunk manager. (Single-only non-virtual function.)
1802 cms.Reset(std::move(extantReleasedChunks));
1803 MOZ_RELEASE_ASSERT(!extantReleasedChunks,
1804 "Released chunk UniquePtr should have been moved-from");
1806 MOZ_RELEASE_ASSERT(
1807 cm.MaxTotalSize() == maxTotalSize,
1808 "MaxTotalSize() should not change when resetting with the same chunk");
1810 // 2nd round, first request. Theoretically async, but this implementation just
1811 // immediately runs the callback.
1812 bool ran = false;
1813 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) {
1814 ran = true;
1815 MOZ_RELEASE_ASSERT(!!aChunk);
1816 chunk = std::move(aChunk);
1818 MOZ_RELEASE_ASSERT(ran, "RequestChunk callback not called immediately");
1819 ran = false;
1820 cm.FulfillChunkRequests();
1821 MOZ_RELEASE_ASSERT(!ran, "FulfillChunkRequests should not have any effects");
1822 MOZ_RELEASE_ASSERT(!!chunk, "First chunk request should always work");
1823 MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= ChunkMinBufferBytes,
1824 "Unexpected chunk size");
1825 MOZ_RELEASE_ASSERT(!chunk->GetNext(), "There should only be one chunk");
1826 MOZ_RELEASE_ASSERT(reinterpret_cast<uintptr_t>(chunk.get()) == chunkAddress,
1827 "Requested chunk should be first requested one");
1828 // Verify that chunk is empty and usable.
1829 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 0);
1830 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 0);
1831 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0);
1832 chunk->SetRangeStart(200);
1833 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 200);
1834 Unused << chunk->ReserveInitialBlockAsTail(3);
1835 Unused << chunk->ReserveBlock(4);
1836 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 3);
1837 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 3 + 4);
1839 // Second request.
1840 ran = false;
1841 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) {
1842 ran = true;
1843 MOZ_RELEASE_ASSERT(!aChunk, "Second chunk request should always fail");
1845 MOZ_RELEASE_ASSERT(ran, "RequestChunk callback not called");
1847 // This one does nothing.
1848 cm.ForgetUnreleasedChunks();
1850 // Don't forget to mark chunk "Done" before letting it die.
1851 chunk->MarkDone();
1852 chunk = nullptr;
1854 // Create a tiny chunk and reset the chunk manager with it.
1855 chunk = ProfileBufferChunk::Create(1);
1856 MOZ_RELEASE_ASSERT(!!chunk);
1857 auto tinyChunkSize = chunk->BufferBytes();
1858 MOZ_RELEASE_ASSERT(tinyChunkSize >= 1);
1859 MOZ_RELEASE_ASSERT(tinyChunkSize < ChunkMinBufferBytes);
1860 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0);
1861 chunk->SetRangeStart(300);
1862 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 300);
1863 cms.Reset(std::move(chunk));
1864 MOZ_RELEASE_ASSERT(!chunk, "chunk UniquePtr should have been moved-from");
1865 MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == tinyChunkSize,
1866 "MaxTotalSize() should match the new chunk size");
1867 chunk = cm.GetChunk();
1868 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0, "Got non-recycled chunk");
1870 // Enough testing! Clean-up.
1871 Unused << chunk->ReserveInitialBlockAsTail(0);
1872 chunk->MarkDone();
1873 cm.ForgetUnreleasedChunks();
1875 # ifdef DEBUG
1876 cm.DeregisteredFrom(chunkManagerRegisterer);
1877 # endif // DEBUG
1879 printf("TestChunkManagerSingle done\n");
1882 static void TestChunkManagerWithLocalLimit() {
1883 printf("TestChunkManagerWithLocalLimit...\n");
1885 // Construct a ProfileBufferChunkManagerWithLocalLimit with chunk of minimum
1886 // size >=100, up to 1000 bytes.
1887 constexpr ProfileBufferChunk::Length MaxTotalBytes = 1000;
1888 constexpr ProfileBufferChunk::Length ChunkMinBufferBytes = 100;
1889 ProfileBufferChunkManagerWithLocalLimit cmll{MaxTotalBytes,
1890 ChunkMinBufferBytes};
1892 // Reference to base class, to exercize virtual methods.
1893 ProfileBufferChunkManager& cm = cmll;
1895 # ifdef DEBUG
1896 const char* chunkManagerRegisterer = "TestChunkManagerWithLocalLimit";
1897 cm.RegisteredWith(chunkManagerRegisterer);
1898 # endif // DEBUG
1900 MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == MaxTotalBytes,
1901 "Max total size should be exactly as given");
1903 unsigned destroyedChunks = 0;
1904 unsigned destroyedBytes = 0;
1905 cm.SetChunkDestroyedCallback([&](const ProfileBufferChunk& aChunks) {
1906 for (const ProfileBufferChunk* chunk = &aChunks; chunk;
1907 chunk = chunk->GetNext()) {
1908 destroyedChunks += 1;
1909 destroyedBytes += chunk->BufferBytes();
1913 UniquePtr<ProfileBufferChunk> extantReleasedChunks =
1914 cm.GetExtantReleasedChunks();
1915 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
1917 // First request.
1918 UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk();
1919 MOZ_RELEASE_ASSERT(!!chunk,
1920 "First chunk immediate request should always work");
1921 const auto chunkActualBufferBytes = chunk->BufferBytes();
1922 MOZ_RELEASE_ASSERT(chunkActualBufferBytes >= ChunkMinBufferBytes,
1923 "Unexpected chunk size");
1924 MOZ_RELEASE_ASSERT(!chunk->GetNext(), "There should only be one chunk");
1926 // Keep address, for later checks.
1927 const uintptr_t chunk1Address = reinterpret_cast<uintptr_t>(chunk.get());
1929 extantReleasedChunks = cm.GetExtantReleasedChunks();
1930 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
1932 // Verify that ReleaseChunk accepts zero chunks.
1933 cm.ReleaseChunk(nullptr);
1934 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
1936 // For this test, we need to be able to get at least 2 chunks without hitting
1937 // the limit. (If this failed, it wouldn't necessary be a problem with
1938 // ProfileBufferChunkManagerWithLocalLimit, fiddle with constants at the top
1939 // of this test.)
1940 MOZ_RELEASE_ASSERT(chunkActualBufferBytes < 2 * MaxTotalBytes);
1942 unsigned chunk1ReuseCount = 0;
1944 // We will do enough loops to go through the maximum size a number of times.
1945 const unsigned Rollovers = 3;
1946 const unsigned Loops = Rollovers * MaxTotalBytes / chunkActualBufferBytes;
1947 for (unsigned i = 0; i < Loops; ++i) {
1948 // Add some data to the chunk.
1949 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 0);
1950 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 0);
1951 MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0);
1952 const ProfileBufferIndex index = 1 + i * chunkActualBufferBytes;
1953 chunk->SetRangeStart(index);
1954 MOZ_RELEASE_ASSERT(chunk->RangeStart() == index);
1955 Unused << chunk->ReserveInitialBlockAsTail(1);
1956 Unused << chunk->ReserveBlock(2);
1957 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetFirstBlock == 1);
1958 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mOffsetPastLastBlock == 1 + 2);
1960 // Request a new chunk.
1961 bool ran = false;
1962 UniquePtr<ProfileBufferChunk> newChunk;
1963 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) {
1964 ran = true;
1965 newChunk = std::move(aChunk);
1967 MOZ_RELEASE_ASSERT(
1968 !ran, "RequestChunk should not immediately fulfill the request");
1969 cm.FulfillChunkRequests();
1970 MOZ_RELEASE_ASSERT(ran, "FulfillChunkRequests should invoke the callback");
1971 MOZ_RELEASE_ASSERT(!!newChunk, "Chunk request should always work");
1972 MOZ_RELEASE_ASSERT(newChunk->BufferBytes() == chunkActualBufferBytes,
1973 "Unexpected chunk size");
1974 MOZ_RELEASE_ASSERT(!newChunk->GetNext(), "There should only be one chunk");
1976 // Mark previous chunk done and release it.
1977 WaitUntilTimeStampChanges(); // Force "done" timestamp to change.
1978 chunk->MarkDone();
1979 cm.ReleaseChunk(std::move(chunk));
1981 // And cycle to the new chunk.
1982 chunk = std::move(newChunk);
1984 if (reinterpret_cast<uintptr_t>(chunk.get()) == chunk1Address) {
1985 ++chunk1ReuseCount;
1989 // Expect all rollovers except 1 to destroy chunks.
1990 MOZ_RELEASE_ASSERT(destroyedChunks >= (Rollovers - 1) * MaxTotalBytes /
1991 chunkActualBufferBytes,
1992 "Not enough destroyed chunks");
1993 MOZ_RELEASE_ASSERT(destroyedBytes == destroyedChunks * chunkActualBufferBytes,
1994 "Mismatched destroyed chunks and bytes");
1995 MOZ_RELEASE_ASSERT(chunk1ReuseCount >= (Rollovers - 1),
1996 "Not enough reuse of the first chunks");
1998 // Check that chunk manager is reentrant from request callback.
1999 bool ran = false;
2000 bool ranInner = false;
2001 UniquePtr<ProfileBufferChunk> newChunk;
2002 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) {
2003 ran = true;
2004 MOZ_RELEASE_ASSERT(!!aChunk, "Chunk request should always work");
2005 Unused << aChunk->ReserveInitialBlockAsTail(0);
2006 WaitUntilTimeStampChanges(); // Force "done" timestamp to change.
2007 aChunk->MarkDone();
2008 UniquePtr<ProfileBufferChunk> anotherChunk = cm.GetChunk();
2009 MOZ_RELEASE_ASSERT(!!anotherChunk);
2010 Unused << anotherChunk->ReserveInitialBlockAsTail(0);
2011 WaitUntilTimeStampChanges(); // Force "done" timestamp to change.
2012 anotherChunk->MarkDone();
2013 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) {
2014 ranInner = true;
2015 MOZ_RELEASE_ASSERT(!!aChunk, "Chunk request should always work");
2016 Unused << aChunk->ReserveInitialBlockAsTail(0);
2017 WaitUntilTimeStampChanges(); // Force "done" timestamp to change.
2018 aChunk->MarkDone();
2020 MOZ_RELEASE_ASSERT(
2021 !ranInner, "RequestChunk should not immediately fulfill the request");
2023 MOZ_RELEASE_ASSERT(!ran,
2024 "RequestChunk should not immediately fulfill the request");
2025 MOZ_RELEASE_ASSERT(
2026 !ranInner,
2027 "RequestChunk should not immediately fulfill the inner request");
2028 cm.FulfillChunkRequests();
2029 MOZ_RELEASE_ASSERT(ran, "FulfillChunkRequests should invoke the callback");
2030 MOZ_RELEASE_ASSERT(!ranInner,
2031 "FulfillChunkRequests should not immediately fulfill "
2032 "the inner request");
2033 cm.FulfillChunkRequests();
2034 MOZ_RELEASE_ASSERT(
2035 ran, "2nd FulfillChunkRequests should invoke the inner request callback");
2037 // Enough testing! Clean-up.
2038 Unused << chunk->ReserveInitialBlockAsTail(0);
2039 WaitUntilTimeStampChanges(); // Force "done" timestamp to change.
2040 chunk->MarkDone();
2041 cm.ForgetUnreleasedChunks();
2043 // Special testing of the release algorithm, to make sure released chunks get
2044 // sorted.
2045 constexpr unsigned RandomReleaseChunkLoop = 100;
2046 // Build a vector of chunks, and mark them "done", ready to be released.
2047 Vector<UniquePtr<ProfileBufferChunk>> chunksToRelease;
2048 MOZ_RELEASE_ASSERT(chunksToRelease.reserve(RandomReleaseChunkLoop));
2049 Vector<TimeStamp> chunksTimeStamps;
2050 MOZ_RELEASE_ASSERT(chunksTimeStamps.reserve(RandomReleaseChunkLoop));
2051 for (unsigned i = 0; i < RandomReleaseChunkLoop; ++i) {
2052 UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk();
2053 MOZ_RELEASE_ASSERT(chunk);
2054 Unused << chunk->ReserveInitialBlockAsTail(0);
2055 chunk->MarkDone();
2056 MOZ_RELEASE_ASSERT(!chunk->ChunkHeader().mDoneTimeStamp.IsNull());
2057 chunksTimeStamps.infallibleEmplaceBack(chunk->ChunkHeader().mDoneTimeStamp);
2058 chunksToRelease.infallibleEmplaceBack(std::move(chunk));
2059 if (i % 10 == 0) {
2060 // "Done" timestamps should *usually* increase, let's make extra sure some
2061 // timestamps are actually different.
2062 WaitUntilTimeStampChanges();
2065 // Shuffle the list.
2066 std::random_device randomDevice;
2067 std::mt19937 generator(randomDevice());
2068 std::shuffle(chunksToRelease.begin(), chunksToRelease.end(), generator);
2069 // And release chunks one by one, checking that the list of released chunks
2070 // is always sorted.
2071 printf("TestChunkManagerWithLocalLimit - Shuffle test timestamps:");
2072 for (unsigned i = 0; i < RandomReleaseChunkLoop; ++i) {
2073 printf(" %f", (chunksToRelease[i]->ChunkHeader().mDoneTimeStamp -
2074 TimeStamp::ProcessCreation())
2075 .ToMicroseconds());
2076 cm.ReleaseChunk(std::move(chunksToRelease[i]));
2077 cm.PeekExtantReleasedChunks([i](const ProfileBufferChunk* releasedChunks) {
2078 MOZ_RELEASE_ASSERT(releasedChunks);
2079 unsigned releasedChunkCount = 1;
2080 for (;;) {
2081 const ProfileBufferChunk* nextChunk = releasedChunks->GetNext();
2082 if (!nextChunk) {
2083 break;
2085 ++releasedChunkCount;
2086 MOZ_RELEASE_ASSERT(releasedChunks->ChunkHeader().mDoneTimeStamp <=
2087 nextChunk->ChunkHeader().mDoneTimeStamp);
2088 releasedChunks = nextChunk;
2090 MOZ_RELEASE_ASSERT(releasedChunkCount == i + 1);
2093 printf("\n");
2094 // Finally, the whole list of released chunks should have the exact same
2095 // timestamps as the initial list of "done" chunks.
2096 extantReleasedChunks = cm.GetExtantReleasedChunks();
2097 for (unsigned i = 0; i < RandomReleaseChunkLoop; ++i) {
2098 MOZ_RELEASE_ASSERT(extantReleasedChunks, "Not enough released chunks");
2099 MOZ_RELEASE_ASSERT(extantReleasedChunks->ChunkHeader().mDoneTimeStamp ==
2100 chunksTimeStamps[i]);
2101 Unused << std::exchange(extantReleasedChunks,
2102 extantReleasedChunks->ReleaseNext());
2104 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Too many released chunks");
2106 # ifdef DEBUG
2107 cm.DeregisteredFrom(chunkManagerRegisterer);
2108 # endif // DEBUG
2110 printf("TestChunkManagerWithLocalLimit done\n");
2113 static bool IsSameMetadata(
2114 const ProfileBufferControlledChunkManager::ChunkMetadata& a1,
2115 const ProfileBufferControlledChunkManager::ChunkMetadata& a2) {
2116 return a1.mDoneTimeStamp == a2.mDoneTimeStamp &&
2117 a1.mBufferBytes == a2.mBufferBytes;
2120 static bool IsSameUpdate(
2121 const ProfileBufferControlledChunkManager::Update& a1,
2122 const ProfileBufferControlledChunkManager::Update& a2) {
2123 // Final and not-an-update don't carry other data, so we can test these two
2124 // states first.
2125 if (a1.IsFinal() || a2.IsFinal()) {
2126 return a1.IsFinal() && a2.IsFinal();
2128 if (a1.IsNotUpdate() || a2.IsNotUpdate()) {
2129 return a1.IsNotUpdate() && a2.IsNotUpdate();
2132 // Here, both are "normal" udpates, check member variables:
2134 if (a1.UnreleasedBytes() != a2.UnreleasedBytes()) {
2135 return false;
2137 if (a1.ReleasedBytes() != a2.ReleasedBytes()) {
2138 return false;
2140 if (a1.OldestDoneTimeStamp() != a2.OldestDoneTimeStamp()) {
2141 return false;
2143 if (a1.NewlyReleasedChunksRef().size() !=
2144 a2.NewlyReleasedChunksRef().size()) {
2145 return false;
2147 for (unsigned i = 0; i < a1.NewlyReleasedChunksRef().size(); ++i) {
2148 if (!IsSameMetadata(a1.NewlyReleasedChunksRef()[i],
2149 a2.NewlyReleasedChunksRef()[i])) {
2150 return false;
2153 return true;
2156 static void TestControlledChunkManagerUpdate() {
2157 printf("TestControlledChunkManagerUpdate...\n");
2159 using Update = ProfileBufferControlledChunkManager::Update;
2161 // Default construction.
2162 Update update1;
2163 MOZ_RELEASE_ASSERT(update1.IsNotUpdate());
2164 MOZ_RELEASE_ASSERT(!update1.IsFinal());
2166 // Clear an already-cleared update.
2167 update1.Clear();
2168 MOZ_RELEASE_ASSERT(update1.IsNotUpdate());
2169 MOZ_RELEASE_ASSERT(!update1.IsFinal());
2171 // Final construction with nullptr.
2172 const Update final(nullptr);
2173 MOZ_RELEASE_ASSERT(final.IsFinal());
2174 MOZ_RELEASE_ASSERT(!final.IsNotUpdate());
2176 // Copy final to cleared.
2177 update1 = final;
2178 MOZ_RELEASE_ASSERT(update1.IsFinal());
2179 MOZ_RELEASE_ASSERT(!update1.IsNotUpdate());
2181 // Copy final to final.
2182 update1 = final;
2183 MOZ_RELEASE_ASSERT(update1.IsFinal());
2184 MOZ_RELEASE_ASSERT(!update1.IsNotUpdate());
2186 // Clear a final update.
2187 update1.Clear();
2188 MOZ_RELEASE_ASSERT(update1.IsNotUpdate());
2189 MOZ_RELEASE_ASSERT(!update1.IsFinal());
2191 // Move final to cleared.
2192 update1 = Update(nullptr);
2193 MOZ_RELEASE_ASSERT(update1.IsFinal());
2194 MOZ_RELEASE_ASSERT(!update1.IsNotUpdate());
2196 // Move final to final.
2197 update1 = Update(nullptr);
2198 MOZ_RELEASE_ASSERT(update1.IsFinal());
2199 MOZ_RELEASE_ASSERT(!update1.IsNotUpdate());
2201 // Move from not-an-update (effectively same as Clear).
2202 update1 = Update();
2203 MOZ_RELEASE_ASSERT(update1.IsNotUpdate());
2204 MOZ_RELEASE_ASSERT(!update1.IsFinal());
2206 auto CreateBiggerChunkAfter = [](const ProfileBufferChunk& aChunkToBeat) {
2207 while (TimeStamp::Now() <= aChunkToBeat.ChunkHeader().mDoneTimeStamp) {
2208 ::SleepMilli(1);
2210 auto chunk = ProfileBufferChunk::Create(aChunkToBeat.BufferBytes() * 2);
2211 MOZ_RELEASE_ASSERT(!!chunk);
2212 MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= aChunkToBeat.BufferBytes() * 2);
2213 Unused << chunk->ReserveInitialBlockAsTail(0);
2214 chunk->MarkDone();
2215 MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mDoneTimeStamp >
2216 aChunkToBeat.ChunkHeader().mDoneTimeStamp);
2217 return chunk;
2220 update1 = Update(1, 2, nullptr, nullptr);
2222 // Create initial update with 2 released chunks and 1 unreleased chunk.
2223 auto released = ProfileBufferChunk::Create(10);
2224 ProfileBufferChunk* c1 = released.get();
2225 Unused << c1->ReserveInitialBlockAsTail(0);
2226 c1->MarkDone();
2228 released->SetLast(CreateBiggerChunkAfter(*c1));
2229 ProfileBufferChunk* c2 = c1->GetNext();
2231 auto unreleased = CreateBiggerChunkAfter(*c2);
2232 ProfileBufferChunk* c3 = unreleased.get();
2234 Update update2(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(), c1,
2235 c1);
2236 MOZ_RELEASE_ASSERT(IsSameUpdate(
2237 update2,
2238 Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(),
2239 c1->ChunkHeader().mDoneTimeStamp,
2240 {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()},
2241 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}})));
2242 // Check every field, this time only, after that we'll trust that the
2243 // `SameUpdate` test will be enough.
2244 MOZ_RELEASE_ASSERT(!update2.IsNotUpdate());
2245 MOZ_RELEASE_ASSERT(!update2.IsFinal());
2246 MOZ_RELEASE_ASSERT(update2.UnreleasedBytes() == c3->BufferBytes());
2247 MOZ_RELEASE_ASSERT(update2.ReleasedBytes() ==
2248 c1->BufferBytes() + c2->BufferBytes());
2249 MOZ_RELEASE_ASSERT(update2.OldestDoneTimeStamp() ==
2250 c1->ChunkHeader().mDoneTimeStamp);
2251 MOZ_RELEASE_ASSERT(update2.NewlyReleasedChunksRef().size() == 2);
2252 MOZ_RELEASE_ASSERT(
2253 IsSameMetadata(update2.NewlyReleasedChunksRef()[0],
2254 {c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()}));
2255 MOZ_RELEASE_ASSERT(
2256 IsSameMetadata(update2.NewlyReleasedChunksRef()[1],
2257 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}));
2259 // Fold into not-an-update.
2260 update1.Fold(std::move(update2));
2261 MOZ_RELEASE_ASSERT(IsSameUpdate(
2262 update1,
2263 Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(),
2264 c1->ChunkHeader().mDoneTimeStamp,
2265 {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()},
2266 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}})));
2268 // Pretend nothing happened.
2269 update2 = Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(), c1,
2270 nullptr);
2271 MOZ_RELEASE_ASSERT(IsSameUpdate(
2272 update2, Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(),
2273 c1->ChunkHeader().mDoneTimeStamp, {})));
2274 update1.Fold(std::move(update2));
2275 MOZ_RELEASE_ASSERT(IsSameUpdate(
2276 update1,
2277 Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(),
2278 c1->ChunkHeader().mDoneTimeStamp,
2279 {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()},
2280 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}})));
2282 // Pretend there's a new unreleased chunk.
2283 c3->SetLast(CreateBiggerChunkAfter(*c3));
2284 ProfileBufferChunk* c4 = c3->GetNext();
2285 update2 = Update(c3->BufferBytes() + c4->BufferBytes(),
2286 c1->BufferBytes() + c2->BufferBytes(), c1, nullptr);
2287 MOZ_RELEASE_ASSERT(
2288 IsSameUpdate(update2, Update(c3->BufferBytes() + c4->BufferBytes(),
2289 c1->BufferBytes() + c2->BufferBytes(),
2290 c1->ChunkHeader().mDoneTimeStamp, {})));
2291 update1.Fold(std::move(update2));
2292 MOZ_RELEASE_ASSERT(IsSameUpdate(
2293 update1,
2294 Update(c3->BufferBytes() + c4->BufferBytes(),
2295 c1->BufferBytes() + c2->BufferBytes(),
2296 c1->ChunkHeader().mDoneTimeStamp,
2297 {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()},
2298 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}})));
2300 // Pretend the first unreleased chunk c3 has been released.
2301 released->SetLast(std::exchange(unreleased, unreleased->ReleaseNext()));
2302 update2 =
2303 Update(c4->BufferBytes(),
2304 c1->BufferBytes() + c2->BufferBytes() + c3->BufferBytes(), c1, c3);
2305 MOZ_RELEASE_ASSERT(IsSameUpdate(
2306 update2,
2307 Update(c4->BufferBytes(),
2308 c1->BufferBytes() + c2->BufferBytes() + c3->BufferBytes(),
2309 c1->ChunkHeader().mDoneTimeStamp,
2310 {{c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()}})));
2311 update1.Fold(std::move(update2));
2312 MOZ_RELEASE_ASSERT(IsSameUpdate(
2313 update1,
2314 Update(c4->BufferBytes(),
2315 c1->BufferBytes() + c2->BufferBytes() + c3->BufferBytes(),
2316 c1->ChunkHeader().mDoneTimeStamp,
2317 {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()},
2318 {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()},
2319 {c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()}})));
2321 // Pretend c1 has been destroyed, so the oldest timestamp is now at c2.
2322 released = released->ReleaseNext();
2323 c1 = nullptr;
2324 update2 = Update(c4->BufferBytes(), c2->BufferBytes() + c3->BufferBytes(), c2,
2325 nullptr);
2326 MOZ_RELEASE_ASSERT(IsSameUpdate(
2327 update2, Update(c4->BufferBytes(), c2->BufferBytes() + c3->BufferBytes(),
2328 c2->ChunkHeader().mDoneTimeStamp, {})));
2329 update1.Fold(std::move(update2));
2330 MOZ_RELEASE_ASSERT(IsSameUpdate(
2331 update1,
2332 Update(c4->BufferBytes(), c2->BufferBytes() + c3->BufferBytes(),
2333 c2->ChunkHeader().mDoneTimeStamp,
2334 {{c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()},
2335 {c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()}})));
2337 // Pretend c2 has been recycled to make unreleased c5, and c4 has been
2338 // released.
2339 auto recycled = std::exchange(released, released->ReleaseNext());
2340 recycled->MarkRecycled();
2341 Unused << recycled->ReserveInitialBlockAsTail(0);
2342 recycled->MarkDone();
2343 released->SetLast(std::move(unreleased));
2344 unreleased = std::move(recycled);
2345 ProfileBufferChunk* c5 = c2;
2346 c2 = nullptr;
2347 update2 =
2348 Update(c5->BufferBytes(), c3->BufferBytes() + c4->BufferBytes(), c3, c4);
2349 MOZ_RELEASE_ASSERT(IsSameUpdate(
2350 update2,
2351 Update(c5->BufferBytes(), c3->BufferBytes() + c4->BufferBytes(),
2352 c3->ChunkHeader().mDoneTimeStamp,
2353 {{c4->ChunkHeader().mDoneTimeStamp, c4->BufferBytes()}})));
2354 update1.Fold(std::move(update2));
2355 MOZ_RELEASE_ASSERT(IsSameUpdate(
2356 update1,
2357 Update(c5->BufferBytes(), c3->BufferBytes() + c4->BufferBytes(),
2358 c3->ChunkHeader().mDoneTimeStamp,
2359 {{c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()},
2360 {c4->ChunkHeader().mDoneTimeStamp, c4->BufferBytes()}})));
2362 // And send a final update.
2363 update1.Fold(Update(nullptr));
2364 MOZ_RELEASE_ASSERT(update1.IsFinal());
2365 MOZ_RELEASE_ASSERT(!update1.IsNotUpdate());
2367 printf("TestControlledChunkManagerUpdate done\n");
2370 static void TestControlledChunkManagerWithLocalLimit() {
2371 printf("TestControlledChunkManagerWithLocalLimit...\n");
2373 // Construct a ProfileBufferChunkManagerWithLocalLimit with chunk of minimum
2374 // size >=100, up to 1000 bytes.
2375 constexpr ProfileBufferChunk::Length MaxTotalBytes = 1000;
2376 constexpr ProfileBufferChunk::Length ChunkMinBufferBytes = 100;
2377 ProfileBufferChunkManagerWithLocalLimit cmll{MaxTotalBytes,
2378 ChunkMinBufferBytes};
2380 // Reference to chunk manager base class.
2381 ProfileBufferChunkManager& cm = cmll;
2383 // Reference to controlled chunk manager base class.
2384 ProfileBufferControlledChunkManager& ccm = cmll;
2386 # ifdef DEBUG
2387 const char* chunkManagerRegisterer =
2388 "TestControlledChunkManagerWithLocalLimit";
2389 cm.RegisteredWith(chunkManagerRegisterer);
2390 # endif // DEBUG
2392 MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == MaxTotalBytes,
2393 "Max total size should be exactly as given");
2395 unsigned destroyedChunks = 0;
2396 unsigned destroyedBytes = 0;
2397 cm.SetChunkDestroyedCallback([&](const ProfileBufferChunk& aChunks) {
2398 for (const ProfileBufferChunk* chunk = &aChunks; chunk;
2399 chunk = chunk->GetNext()) {
2400 destroyedChunks += 1;
2401 destroyedBytes += chunk->BufferBytes();
2405 using Update = ProfileBufferControlledChunkManager::Update;
2406 unsigned updateCount = 0;
2407 ProfileBufferControlledChunkManager::Update update;
2408 MOZ_RELEASE_ASSERT(update.IsNotUpdate());
2409 auto updateCallback = [&](Update&& aUpdate) {
2410 ++updateCount;
2411 update.Fold(std::move(aUpdate));
2413 ccm.SetUpdateCallback(updateCallback);
2414 MOZ_RELEASE_ASSERT(updateCount == 1,
2415 "SetUpdateCallback should have triggered an update");
2416 MOZ_RELEASE_ASSERT(IsSameUpdate(update, Update(0, 0, TimeStamp{}, {})));
2417 updateCount = 0;
2418 update.Clear();
2420 UniquePtr<ProfileBufferChunk> extantReleasedChunks =
2421 cm.GetExtantReleasedChunks();
2422 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
2423 MOZ_RELEASE_ASSERT(updateCount == 1,
2424 "GetExtantReleasedChunks should have triggered an update");
2425 MOZ_RELEASE_ASSERT(IsSameUpdate(update, Update(0, 0, TimeStamp{}, {})));
2426 updateCount = 0;
2427 update.Clear();
2429 // First request.
2430 UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk();
2431 MOZ_RELEASE_ASSERT(!!chunk,
2432 "First chunk immediate request should always work");
2433 const auto chunkActualBufferBytes = chunk->BufferBytes();
2434 MOZ_RELEASE_ASSERT(updateCount == 1,
2435 "GetChunk should have triggered an update");
2436 MOZ_RELEASE_ASSERT(
2437 IsSameUpdate(update, Update(chunk->BufferBytes(), 0, TimeStamp{}, {})));
2438 updateCount = 0;
2439 update.Clear();
2441 extantReleasedChunks = cm.GetExtantReleasedChunks();
2442 MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
2443 MOZ_RELEASE_ASSERT(updateCount == 1,
2444 "GetExtantReleasedChunks should have triggered an update");
2445 MOZ_RELEASE_ASSERT(
2446 IsSameUpdate(update, Update(chunk->BufferBytes(), 0, TimeStamp{}, {})));
2447 updateCount = 0;
2448 update.Clear();
2450 // For this test, we need to be able to get at least 2 chunks without hitting
2451 // the limit. (If this failed, it wouldn't necessary be a problem with
2452 // ProfileBufferChunkManagerWithLocalLimit, fiddle with constants at the top
2453 // of this test.)
2454 MOZ_RELEASE_ASSERT(chunkActualBufferBytes < 2 * MaxTotalBytes);
2456 ProfileBufferChunk::Length previousUnreleasedBytes = chunk->BufferBytes();
2457 ProfileBufferChunk::Length previousReleasedBytes = 0;
2458 TimeStamp previousOldestDoneTimeStamp;
2460 // We will do enough loops to go through the maximum size a number of times.
2461 const unsigned Rollovers = 3;
2462 const unsigned Loops = Rollovers * MaxTotalBytes / chunkActualBufferBytes;
2463 for (unsigned i = 0; i < Loops; ++i) {
2464 // Add some data to the chunk.
2465 const ProfileBufferIndex index =
2466 ProfileBufferIndex(chunkActualBufferBytes) * i + 1;
2467 chunk->SetRangeStart(index);
2468 Unused << chunk->ReserveInitialBlockAsTail(1);
2469 Unused << chunk->ReserveBlock(2);
2471 // Request a new chunk.
2472 UniquePtr<ProfileBufferChunk> newChunk;
2473 cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) {
2474 newChunk = std::move(aChunk);
2476 MOZ_RELEASE_ASSERT(updateCount == 0,
2477 "RequestChunk() shouldn't have triggered an update");
2478 cm.FulfillChunkRequests();
2479 MOZ_RELEASE_ASSERT(!!newChunk, "Chunk request should always work");
2480 MOZ_RELEASE_ASSERT(newChunk->BufferBytes() == chunkActualBufferBytes,
2481 "Unexpected chunk size");
2482 MOZ_RELEASE_ASSERT(!newChunk->GetNext(), "There should only be one chunk");
2484 MOZ_RELEASE_ASSERT(updateCount == 1,
2485 "FulfillChunkRequests() after a request should have "
2486 "triggered an update");
2487 MOZ_RELEASE_ASSERT(!update.IsFinal());
2488 MOZ_RELEASE_ASSERT(!update.IsNotUpdate());
2489 MOZ_RELEASE_ASSERT(update.UnreleasedBytes() ==
2490 previousUnreleasedBytes + newChunk->BufferBytes());
2491 previousUnreleasedBytes = update.UnreleasedBytes();
2492 MOZ_RELEASE_ASSERT(update.ReleasedBytes() <= previousReleasedBytes);
2493 previousReleasedBytes = update.ReleasedBytes();
2494 MOZ_RELEASE_ASSERT(previousOldestDoneTimeStamp.IsNull() ||
2495 update.OldestDoneTimeStamp() >=
2496 previousOldestDoneTimeStamp);
2497 previousOldestDoneTimeStamp = update.OldestDoneTimeStamp();
2498 MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().empty());
2499 updateCount = 0;
2500 update.Clear();
2502 // Make sure the "Done" timestamp below cannot be the same as from the
2503 // previous loop.
2504 const TimeStamp now = TimeStamp::Now();
2505 while (TimeStamp::Now() == now) {
2506 ::SleepMilli(1);
2509 // Mark previous chunk done and release it.
2510 WaitUntilTimeStampChanges(); // Force "done" timestamp to change.
2511 chunk->MarkDone();
2512 const auto doneTimeStamp = chunk->ChunkHeader().mDoneTimeStamp;
2513 const auto bufferBytes = chunk->BufferBytes();
2514 cm.ReleaseChunk(std::move(chunk));
2516 MOZ_RELEASE_ASSERT(updateCount == 1,
2517 "ReleaseChunk() should have triggered an update");
2518 MOZ_RELEASE_ASSERT(!update.IsFinal());
2519 MOZ_RELEASE_ASSERT(!update.IsNotUpdate());
2520 MOZ_RELEASE_ASSERT(update.UnreleasedBytes() ==
2521 previousUnreleasedBytes - bufferBytes);
2522 previousUnreleasedBytes = update.UnreleasedBytes();
2523 MOZ_RELEASE_ASSERT(update.ReleasedBytes() ==
2524 previousReleasedBytes + bufferBytes);
2525 previousReleasedBytes = update.ReleasedBytes();
2526 MOZ_RELEASE_ASSERT(previousOldestDoneTimeStamp.IsNull() ||
2527 update.OldestDoneTimeStamp() >=
2528 previousOldestDoneTimeStamp);
2529 previousOldestDoneTimeStamp = update.OldestDoneTimeStamp();
2530 MOZ_RELEASE_ASSERT(update.OldestDoneTimeStamp() <= doneTimeStamp);
2531 MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().size() == 1);
2532 MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef()[0].mDoneTimeStamp ==
2533 doneTimeStamp);
2534 MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef()[0].mBufferBytes ==
2535 bufferBytes);
2536 updateCount = 0;
2537 update.Clear();
2539 // And cycle to the new chunk.
2540 chunk = std::move(newChunk);
2543 // Enough testing! Clean-up.
2544 Unused << chunk->ReserveInitialBlockAsTail(0);
2545 chunk->MarkDone();
2546 cm.ForgetUnreleasedChunks();
2547 MOZ_RELEASE_ASSERT(
2548 updateCount == 1,
2549 "ForgetUnreleasedChunks() should have triggered an update");
2550 MOZ_RELEASE_ASSERT(!update.IsFinal());
2551 MOZ_RELEASE_ASSERT(!update.IsNotUpdate());
2552 MOZ_RELEASE_ASSERT(update.UnreleasedBytes() == 0);
2553 MOZ_RELEASE_ASSERT(update.ReleasedBytes() == previousReleasedBytes);
2554 MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().empty() == 1);
2555 updateCount = 0;
2556 update.Clear();
2558 ccm.SetUpdateCallback({});
2559 MOZ_RELEASE_ASSERT(updateCount == 1,
2560 "SetUpdateCallback({}) should have triggered an update");
2561 MOZ_RELEASE_ASSERT(update.IsFinal());
2563 # ifdef DEBUG
2564 cm.DeregisteredFrom(chunkManagerRegisterer);
2565 # endif // DEBUG
2567 printf("TestControlledChunkManagerWithLocalLimit done\n");
2570 # define VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED( \
2571 aProfileChunkedBuffer, aStart, aEnd, aPushed, aCleared, aFailed) \
2573 ProfileChunkedBuffer::State state = (aProfileChunkedBuffer).GetState(); \
2574 MOZ_RELEASE_ASSERT(state.mRangeStart == (aStart)); \
2575 MOZ_RELEASE_ASSERT(state.mRangeEnd == (aEnd)); \
2576 MOZ_RELEASE_ASSERT(state.mPushedBlockCount == (aPushed)); \
2577 MOZ_RELEASE_ASSERT(state.mClearedBlockCount == (aCleared)); \
2578 MOZ_RELEASE_ASSERT(state.mFailedPutBytes == (aFailed)); \
2581 static void TestChunkedBuffer() {
2582 printf("TestChunkedBuffer...\n");
2584 ProfileBufferBlockIndex blockIndex;
2585 MOZ_RELEASE_ASSERT(!blockIndex);
2586 MOZ_RELEASE_ASSERT(blockIndex == nullptr);
2588 // Create an out-of-session ProfileChunkedBuffer.
2589 ProfileChunkedBuffer cb(ProfileChunkedBuffer::ThreadSafety::WithMutex);
2591 MOZ_RELEASE_ASSERT(cb.BufferLength().isNothing());
2593 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0);
2595 int result = 0;
2596 result = cb.ReserveAndPut(
2597 []() {
2598 MOZ_RELEASE_ASSERT(false);
2599 return 1;
2601 [](Maybe<ProfileBufferEntryWriter>& aEW) { return aEW ? 2 : 3; });
2602 MOZ_RELEASE_ASSERT(result == 3);
2603 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0);
2605 result = 0;
2606 result = cb.Put(
2607 1, [](Maybe<ProfileBufferEntryWriter>& aEW) { return aEW ? 1 : 2; });
2608 MOZ_RELEASE_ASSERT(result == 2);
2609 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0);
2611 blockIndex = cb.PutFrom(&result, 1);
2612 MOZ_RELEASE_ASSERT(!blockIndex);
2613 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0);
2615 blockIndex = cb.PutObjects(123, result, "hello");
2616 MOZ_RELEASE_ASSERT(!blockIndex);
2617 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0);
2619 blockIndex = cb.PutObject(123);
2620 MOZ_RELEASE_ASSERT(!blockIndex);
2621 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0);
2623 auto chunks = cb.GetAllChunks();
2624 static_assert(std::is_same_v<decltype(chunks), UniquePtr<ProfileBufferChunk>>,
2625 "ProfileChunkedBuffer::GetAllChunks() should return a "
2626 "UniquePtr<ProfileBufferChunk>");
2627 MOZ_RELEASE_ASSERT(!chunks, "Expected no chunks when out-of-session");
2629 bool ran = false;
2630 result = 0;
2631 result = cb.Read([&](ProfileChunkedBuffer::Reader* aReader) {
2632 ran = true;
2633 MOZ_RELEASE_ASSERT(!aReader);
2634 return 3;
2636 MOZ_RELEASE_ASSERT(ran);
2637 MOZ_RELEASE_ASSERT(result == 3);
2639 cb.ReadEach([](ProfileBufferEntryReader&) { MOZ_RELEASE_ASSERT(false); });
2641 result = 0;
2642 result = cb.ReadAt(nullptr, [](Maybe<ProfileBufferEntryReader>&& er) {
2643 MOZ_RELEASE_ASSERT(er.isNothing());
2644 return 4;
2646 MOZ_RELEASE_ASSERT(result == 4);
2648 // Use ProfileBufferChunkManagerWithLocalLimit, which will give away
2649 // ProfileBufferChunks that can contain 128 bytes, using up to 1KB of memory
2650 // (including usable 128 bytes and headers).
2651 constexpr size_t bufferMaxSize = 1024;
2652 constexpr ProfileChunkedBuffer::Length chunkMinSize = 128;
2653 ProfileBufferChunkManagerWithLocalLimit cm(bufferMaxSize, chunkMinSize);
2654 cb.SetChunkManager(cm);
2655 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0);
2657 // Let the chunk manager fulfill the initial request for an extra chunk.
2658 cm.FulfillChunkRequests();
2660 MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == bufferMaxSize);
2661 MOZ_RELEASE_ASSERT(cb.BufferLength().isSome());
2662 MOZ_RELEASE_ASSERT(*cb.BufferLength() == bufferMaxSize);
2663 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1, 1, 0, 0, 0);
2665 // Write an int with the main `ReserveAndPut` function.
2666 const int test = 123;
2667 ran = false;
2668 blockIndex = nullptr;
2669 bool success = cb.ReserveAndPut(
2670 []() { return sizeof(test); },
2671 [&](Maybe<ProfileBufferEntryWriter>& aEW) {
2672 ran = true;
2673 if (!aEW) {
2674 return false;
2676 blockIndex = aEW->CurrentBlockIndex();
2677 MOZ_RELEASE_ASSERT(aEW->RemainingBytes() == sizeof(test));
2678 aEW->WriteObject(test);
2679 MOZ_RELEASE_ASSERT(aEW->RemainingBytes() == 0);
2680 return true;
2682 MOZ_RELEASE_ASSERT(ran);
2683 MOZ_RELEASE_ASSERT(success);
2684 MOZ_RELEASE_ASSERT(blockIndex.ConvertToProfileBufferIndex() == 1);
2685 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
2686 cb, 1, 1 + ULEB128Size(sizeof(test)) + sizeof(test), 1, 0, 0);
2688 ran = false;
2689 result = 0;
2690 result = cb.Read([&](ProfileChunkedBuffer::Reader* aReader) {
2691 ran = true;
2692 MOZ_RELEASE_ASSERT(!!aReader);
2693 // begin() and end() should be at the range edges (verified above).
2694 MOZ_RELEASE_ASSERT(
2695 aReader->begin().CurrentBlockIndex().ConvertToProfileBufferIndex() ==
2697 MOZ_RELEASE_ASSERT(
2698 aReader->end().CurrentBlockIndex().ConvertToProfileBufferIndex() == 0);
2699 // Null ProfileBufferBlockIndex clamped to the beginning.
2700 MOZ_RELEASE_ASSERT(aReader->At(nullptr) == aReader->begin());
2701 MOZ_RELEASE_ASSERT(aReader->At(blockIndex) == aReader->begin());
2702 // At(begin) same as begin().
2703 MOZ_RELEASE_ASSERT(aReader->At(aReader->begin().CurrentBlockIndex()) ==
2704 aReader->begin());
2705 // At(past block) same as end().
2706 MOZ_RELEASE_ASSERT(
2707 aReader->At(ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
2708 1 + 1 + sizeof(test))) == aReader->end());
2710 size_t read = 0;
2711 aReader->ForEach([&](ProfileBufferEntryReader& er) {
2712 ++read;
2713 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(test));
2714 const auto value = er.ReadObject<decltype(test)>();
2715 MOZ_RELEASE_ASSERT(value == test);
2716 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0);
2718 MOZ_RELEASE_ASSERT(read == 1);
2720 read = 0;
2721 for (auto er : *aReader) {
2722 static_assert(std::is_same_v<decltype(er), ProfileBufferEntryReader>,
2723 "ProfileChunkedBuffer::Reader range-for should produce "
2724 "ProfileBufferEntryReader objects");
2725 ++read;
2726 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(test));
2727 const auto value = er.ReadObject<decltype(test)>();
2728 MOZ_RELEASE_ASSERT(value == test);
2729 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0);
2731 MOZ_RELEASE_ASSERT(read == 1);
2732 return 5;
2734 MOZ_RELEASE_ASSERT(ran);
2735 MOZ_RELEASE_ASSERT(result == 5);
2737 // Read the int directly from the ProfileChunkedBuffer, without block index.
2738 size_t read = 0;
2739 cb.ReadEach([&](ProfileBufferEntryReader& er) {
2740 ++read;
2741 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(test));
2742 const auto value = er.ReadObject<decltype(test)>();
2743 MOZ_RELEASE_ASSERT(value == test);
2744 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0);
2746 MOZ_RELEASE_ASSERT(read == 1);
2748 // Read the int directly from the ProfileChunkedBuffer, with block index.
2749 read = 0;
2750 blockIndex = nullptr;
2751 cb.ReadEach(
2752 [&](ProfileBufferEntryReader& er, ProfileBufferBlockIndex aBlockIndex) {
2753 ++read;
2754 MOZ_RELEASE_ASSERT(!!aBlockIndex);
2755 MOZ_RELEASE_ASSERT(!blockIndex);
2756 blockIndex = aBlockIndex;
2757 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(test));
2758 const auto value = er.ReadObject<decltype(test)>();
2759 MOZ_RELEASE_ASSERT(value == test);
2760 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0);
2762 MOZ_RELEASE_ASSERT(read == 1);
2763 MOZ_RELEASE_ASSERT(!!blockIndex);
2764 MOZ_RELEASE_ASSERT(blockIndex != nullptr);
2766 // Read the int from its block index.
2767 read = 0;
2768 result = 0;
2769 result = cb.ReadAt(blockIndex, [&](Maybe<ProfileBufferEntryReader>&& er) {
2770 ++read;
2771 MOZ_RELEASE_ASSERT(er.isSome());
2772 MOZ_RELEASE_ASSERT(er->CurrentBlockIndex() == blockIndex);
2773 MOZ_RELEASE_ASSERT(!er->NextBlockIndex());
2774 MOZ_RELEASE_ASSERT(er->RemainingBytes() == sizeof(test));
2775 const auto value = er->ReadObject<decltype(test)>();
2776 MOZ_RELEASE_ASSERT(value == test);
2777 MOZ_RELEASE_ASSERT(er->RemainingBytes() == 0);
2778 return 6;
2780 MOZ_RELEASE_ASSERT(result == 6);
2781 MOZ_RELEASE_ASSERT(read == 1);
2783 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(ProfileBufferIndex{}));
2784 MOZ_RELEASE_ASSERT(
2785 cb.IsIndexInCurrentChunk(blockIndex.ConvertToProfileBufferIndex()));
2786 MOZ_RELEASE_ASSERT(cb.IsIndexInCurrentChunk(cb.GetState().mRangeEnd - 1));
2787 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(cb.GetState().mRangeEnd));
2789 // No changes after reads.
2790 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
2791 cb, 1, 1 + ULEB128Size(sizeof(test)) + sizeof(test), 1, 0, 0);
2793 // Steal the underlying ProfileBufferChunks from the ProfileChunkedBuffer.
2794 chunks = cb.GetAllChunks();
2795 MOZ_RELEASE_ASSERT(!!chunks, "Expected at least one chunk");
2796 MOZ_RELEASE_ASSERT(!!chunks->GetNext(), "Expected two chunks");
2797 MOZ_RELEASE_ASSERT(!chunks->GetNext()->GetNext(), "Expected only two chunks");
2798 const ProfileChunkedBuffer::Length chunkActualSize = chunks->BufferBytes();
2799 MOZ_RELEASE_ASSERT(chunkActualSize >= chunkMinSize);
2800 MOZ_RELEASE_ASSERT(chunks->RangeStart() == 1);
2801 MOZ_RELEASE_ASSERT(chunks->OffsetFirstBlock() == 0);
2802 MOZ_RELEASE_ASSERT(chunks->OffsetPastLastBlock() == 1 + sizeof(test));
2804 // GetAllChunks() should have advanced the index one full chunk forward.
2805 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1 + chunkActualSize,
2806 1 + chunkActualSize, 1, 0, 0);
2808 // Nothing more to read from the now-empty ProfileChunkedBuffer.
2809 cb.ReadEach([](ProfileBufferEntryReader&) { MOZ_RELEASE_ASSERT(false); });
2810 cb.ReadEach([](ProfileBufferEntryReader&, ProfileBufferBlockIndex) {
2811 MOZ_RELEASE_ASSERT(false);
2813 result = 0;
2814 result = cb.ReadAt(nullptr, [](Maybe<ProfileBufferEntryReader>&& er) {
2815 MOZ_RELEASE_ASSERT(er.isNothing());
2816 return 7;
2818 MOZ_RELEASE_ASSERT(result == 7);
2820 // Read the int from the stolen chunks.
2821 read = 0;
2822 ProfileChunkedBuffer::ReadEach(
2823 chunks.get(), nullptr,
2824 [&](ProfileBufferEntryReader& er, ProfileBufferBlockIndex aBlockIndex) {
2825 ++read;
2826 MOZ_RELEASE_ASSERT(aBlockIndex == blockIndex);
2827 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(test));
2828 const auto value = er.ReadObject<decltype(test)>();
2829 MOZ_RELEASE_ASSERT(value == test);
2830 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0);
2832 MOZ_RELEASE_ASSERT(read == 1);
2834 // No changes after reads.
2835 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, 1 + chunkActualSize,
2836 1 + chunkActualSize, 1, 0, 0);
2838 // Write lots of numbers (by memcpy), which should trigger Chunk destructions.
2839 ProfileBufferBlockIndex firstBlockIndex;
2840 MOZ_RELEASE_ASSERT(!firstBlockIndex);
2841 ProfileBufferBlockIndex lastBlockIndex;
2842 MOZ_RELEASE_ASSERT(!lastBlockIndex);
2843 const size_t lots = 2 * bufferMaxSize / (1 + sizeof(int));
2844 for (size_t i = 1; i < lots; ++i) {
2845 ProfileBufferBlockIndex blockIndex = cb.PutFrom(&i, sizeof(i));
2846 MOZ_RELEASE_ASSERT(!!blockIndex);
2847 MOZ_RELEASE_ASSERT(blockIndex > firstBlockIndex);
2848 if (!firstBlockIndex) {
2849 firstBlockIndex = blockIndex;
2851 MOZ_RELEASE_ASSERT(blockIndex > lastBlockIndex);
2852 lastBlockIndex = blockIndex;
2855 ProfileChunkedBuffer::State stateAfterPuts = cb.GetState();
2856 ProfileBufferIndex startAfterPuts = stateAfterPuts.mRangeStart;
2857 MOZ_RELEASE_ASSERT(startAfterPuts > 1 + chunkActualSize);
2858 ProfileBufferIndex endAfterPuts = stateAfterPuts.mRangeEnd;
2859 MOZ_RELEASE_ASSERT(endAfterPuts > startAfterPuts);
2860 uint64_t pushedAfterPuts = stateAfterPuts.mPushedBlockCount;
2861 MOZ_RELEASE_ASSERT(pushedAfterPuts > 0);
2862 uint64_t clearedAfterPuts = stateAfterPuts.mClearedBlockCount;
2863 MOZ_RELEASE_ASSERT(clearedAfterPuts > 0);
2864 MOZ_RELEASE_ASSERT(stateAfterPuts.mFailedPutBytes == 0);
2865 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(ProfileBufferIndex{}));
2866 MOZ_RELEASE_ASSERT(
2867 !cb.IsIndexInCurrentChunk(blockIndex.ConvertToProfileBufferIndex()));
2868 MOZ_RELEASE_ASSERT(
2869 !cb.IsIndexInCurrentChunk(firstBlockIndex.ConvertToProfileBufferIndex()));
2871 // Read extant numbers, which should at least follow each other.
2872 read = 0;
2873 size_t i = 0;
2874 cb.ReadEach(
2875 [&](ProfileBufferEntryReader& er, ProfileBufferBlockIndex aBlockIndex) {
2876 ++read;
2877 MOZ_RELEASE_ASSERT(!!aBlockIndex);
2878 MOZ_RELEASE_ASSERT(aBlockIndex > firstBlockIndex);
2879 MOZ_RELEASE_ASSERT(aBlockIndex <= lastBlockIndex);
2880 MOZ_RELEASE_ASSERT(er.RemainingBytes() == sizeof(size_t));
2881 const auto value = er.ReadObject<size_t>();
2882 if (i == 0) {
2883 i = value;
2884 } else {
2885 MOZ_RELEASE_ASSERT(value == ++i);
2887 MOZ_RELEASE_ASSERT(er.RemainingBytes() == 0);
2889 MOZ_RELEASE_ASSERT(read != 0);
2890 MOZ_RELEASE_ASSERT(read < lots);
2892 // Read first extant number.
2893 read = 0;
2894 i = 0;
2895 blockIndex = nullptr;
2896 success =
2897 cb.ReadAt(firstBlockIndex, [&](Maybe<ProfileBufferEntryReader>&& er) {
2898 MOZ_ASSERT(er.isSome());
2899 ++read;
2900 MOZ_RELEASE_ASSERT(er->CurrentBlockIndex() > firstBlockIndex);
2901 MOZ_RELEASE_ASSERT(!!er->NextBlockIndex());
2902 MOZ_RELEASE_ASSERT(er->NextBlockIndex() > firstBlockIndex);
2903 MOZ_RELEASE_ASSERT(er->NextBlockIndex() < lastBlockIndex);
2904 blockIndex = er->NextBlockIndex();
2905 MOZ_RELEASE_ASSERT(er->RemainingBytes() == sizeof(size_t));
2906 const auto value = er->ReadObject<size_t>();
2907 MOZ_RELEASE_ASSERT(i == 0);
2908 i = value;
2909 MOZ_RELEASE_ASSERT(er->RemainingBytes() == 0);
2910 return 7;
2912 MOZ_RELEASE_ASSERT(success);
2913 MOZ_RELEASE_ASSERT(read == 1);
2914 // Read other extant numbers one by one.
2915 do {
2916 bool success =
2917 cb.ReadAt(blockIndex, [&](Maybe<ProfileBufferEntryReader>&& er) {
2918 MOZ_ASSERT(er.isSome());
2919 ++read;
2920 MOZ_RELEASE_ASSERT(er->CurrentBlockIndex() == blockIndex);
2921 MOZ_RELEASE_ASSERT(!er->NextBlockIndex() ||
2922 er->NextBlockIndex() > blockIndex);
2923 MOZ_RELEASE_ASSERT(!er->NextBlockIndex() ||
2924 er->NextBlockIndex() > firstBlockIndex);
2925 MOZ_RELEASE_ASSERT(!er->NextBlockIndex() ||
2926 er->NextBlockIndex() <= lastBlockIndex);
2927 MOZ_RELEASE_ASSERT(er->NextBlockIndex()
2928 ? blockIndex < lastBlockIndex
2929 : blockIndex == lastBlockIndex,
2930 "er->NextBlockIndex() should only be null when "
2931 "blockIndex is at the last block");
2932 blockIndex = er->NextBlockIndex();
2933 MOZ_RELEASE_ASSERT(er->RemainingBytes() == sizeof(size_t));
2934 const auto value = er->ReadObject<size_t>();
2935 MOZ_RELEASE_ASSERT(value == ++i);
2936 MOZ_RELEASE_ASSERT(er->RemainingBytes() == 0);
2937 return true;
2939 MOZ_RELEASE_ASSERT(success);
2940 } while (blockIndex);
2941 MOZ_RELEASE_ASSERT(read > 1);
2943 // No changes after reads.
2944 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
2945 cb, startAfterPuts, endAfterPuts, pushedAfterPuts, clearedAfterPuts, 0);
2947 # ifdef DEBUG
2948 // cb.Dump();
2949 # endif
2951 cb.Clear();
2953 # ifdef DEBUG
2954 // cb.Dump();
2955 # endif
2957 ProfileChunkedBuffer::State stateAfterClear = cb.GetState();
2958 ProfileBufferIndex startAfterClear = stateAfterClear.mRangeStart;
2959 MOZ_RELEASE_ASSERT(startAfterClear > startAfterPuts);
2960 ProfileBufferIndex endAfterClear = stateAfterClear.mRangeEnd;
2961 MOZ_RELEASE_ASSERT(endAfterClear == startAfterClear);
2962 MOZ_RELEASE_ASSERT(stateAfterClear.mPushedBlockCount == 0);
2963 MOZ_RELEASE_ASSERT(stateAfterClear.mClearedBlockCount == 0);
2964 MOZ_RELEASE_ASSERT(stateAfterClear.mFailedPutBytes == 0);
2965 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(ProfileBufferIndex{}));
2966 MOZ_RELEASE_ASSERT(
2967 !cb.IsIndexInCurrentChunk(blockIndex.ConvertToProfileBufferIndex()));
2968 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(stateAfterClear.mRangeEnd - 1));
2969 MOZ_RELEASE_ASSERT(!cb.IsIndexInCurrentChunk(stateAfterClear.mRangeEnd));
2971 // Start writer threads.
2972 constexpr int ThreadCount = 32;
2973 std::thread threads[ThreadCount];
2974 for (int threadNo = 0; threadNo < ThreadCount; ++threadNo) {
2975 threads[threadNo] = std::thread(
2976 [&](int aThreadNo) {
2977 ::SleepMilli(1);
2978 constexpr int pushCount = 1024;
2979 for (int push = 0; push < pushCount; ++push) {
2980 // Reserve as many bytes as the thread number (but at least enough
2981 // to store an int), and write an increasing int.
2982 const bool success =
2983 cb.Put(std::max(aThreadNo, int(sizeof(push))),
2984 [&](Maybe<ProfileBufferEntryWriter>& aEW) {
2985 if (!aEW) {
2986 return false;
2988 aEW->WriteObject(aThreadNo * 1000000 + push);
2989 // Advance writer to the end.
2990 for (size_t r = aEW->RemainingBytes(); r != 0; --r) {
2991 aEW->WriteObject<char>('_');
2993 return true;
2995 MOZ_RELEASE_ASSERT(success);
2998 threadNo);
3001 // Wait for all writer threads to die.
3002 for (auto&& thread : threads) {
3003 thread.join();
3006 # ifdef DEBUG
3007 // cb.Dump();
3008 # endif
3010 ProfileChunkedBuffer::State stateAfterMTPuts = cb.GetState();
3011 ProfileBufferIndex startAfterMTPuts = stateAfterMTPuts.mRangeStart;
3012 MOZ_RELEASE_ASSERT(startAfterMTPuts > startAfterClear);
3013 ProfileBufferIndex endAfterMTPuts = stateAfterMTPuts.mRangeEnd;
3014 MOZ_RELEASE_ASSERT(endAfterMTPuts > startAfterMTPuts);
3015 MOZ_RELEASE_ASSERT(stateAfterMTPuts.mPushedBlockCount > 0);
3016 MOZ_RELEASE_ASSERT(stateAfterMTPuts.mClearedBlockCount > 0);
3017 MOZ_RELEASE_ASSERT(stateAfterMTPuts.mFailedPutBytes == 0);
3019 // Reset to out-of-session.
3020 cb.ResetChunkManager();
3022 ProfileChunkedBuffer::State stateAfterReset = cb.GetState();
3023 ProfileBufferIndex startAfterReset = stateAfterReset.mRangeStart;
3024 MOZ_RELEASE_ASSERT(startAfterReset == endAfterMTPuts);
3025 ProfileBufferIndex endAfterReset = stateAfterReset.mRangeEnd;
3026 MOZ_RELEASE_ASSERT(endAfterReset == startAfterReset);
3027 MOZ_RELEASE_ASSERT(stateAfterReset.mPushedBlockCount == 0);
3028 MOZ_RELEASE_ASSERT(stateAfterReset.mClearedBlockCount == 0);
3029 MOZ_RELEASE_ASSERT(stateAfterReset.mFailedPutBytes == 0);
3031 success = cb.ReserveAndPut(
3032 []() {
3033 MOZ_RELEASE_ASSERT(false);
3034 return 1;
3036 [](Maybe<ProfileBufferEntryWriter>& aEW) { return !!aEW; });
3037 MOZ_RELEASE_ASSERT(!success);
3038 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset,
3039 0, 0, 0);
3041 success =
3042 cb.Put(1, [](Maybe<ProfileBufferEntryWriter>& aEW) { return !!aEW; });
3043 MOZ_RELEASE_ASSERT(!success);
3044 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset,
3045 0, 0, 0);
3047 blockIndex = cb.PutFrom(&success, 1);
3048 MOZ_RELEASE_ASSERT(!blockIndex);
3049 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset,
3050 0, 0, 0);
3052 blockIndex = cb.PutObjects(123, success, "hello");
3053 MOZ_RELEASE_ASSERT(!blockIndex);
3054 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset,
3055 0, 0, 0);
3057 blockIndex = cb.PutObject(123);
3058 MOZ_RELEASE_ASSERT(!blockIndex);
3059 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset,
3060 0, 0, 0);
3062 chunks = cb.GetAllChunks();
3063 MOZ_RELEASE_ASSERT(!chunks, "Expected no chunks when out-of-session");
3064 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset,
3065 0, 0, 0);
3067 cb.ReadEach([](ProfileBufferEntryReader&) { MOZ_RELEASE_ASSERT(false); });
3068 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset,
3069 0, 0, 0);
3071 success = cb.ReadAt(nullptr, [](Maybe<ProfileBufferEntryReader>&& er) {
3072 MOZ_RELEASE_ASSERT(er.isNothing());
3073 return true;
3075 MOZ_RELEASE_ASSERT(success);
3076 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cb, startAfterReset, endAfterReset,
3077 0, 0, 0);
3079 printf("TestChunkedBuffer done\n");
3082 static void TestChunkedBufferSingle() {
3083 printf("TestChunkedBufferSingle...\n");
3085 constexpr ProfileChunkedBuffer::Length chunkMinSize = 128;
3087 // Create a ProfileChunkedBuffer that will own&use a
3088 // ProfileBufferChunkManagerSingle, which will give away one
3089 // ProfileBufferChunk that can contain 128 bytes.
3090 ProfileChunkedBuffer cbSingle(
3091 ProfileChunkedBuffer::ThreadSafety::WithoutMutex,
3092 MakeUnique<ProfileBufferChunkManagerSingle>(chunkMinSize));
3094 MOZ_RELEASE_ASSERT(cbSingle.BufferLength().isSome());
3095 const ProfileChunkedBuffer::Length bufferBytes = *cbSingle.BufferLength();
3096 MOZ_RELEASE_ASSERT(bufferBytes >= chunkMinSize);
3098 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cbSingle, 1, 1, 0, 0, 0);
3100 // We will write this many blocks to fill the chunk.
3101 constexpr size_t testBlocks = 4;
3102 const ProfileChunkedBuffer::Length blockBytes = bufferBytes / testBlocks;
3103 MOZ_RELEASE_ASSERT(ULEB128Size(blockBytes) == 1,
3104 "This test assumes block sizes are small enough so that "
3105 "their ULEB128-encoded size is 1 byte");
3106 const ProfileChunkedBuffer::Length entryBytes =
3107 blockBytes - ULEB128Size(blockBytes);
3109 // First buffer-filling test: Try to write a too-big entry at the end of the
3110 // chunk.
3112 // Write all but one block.
3113 for (size_t i = 0; i < testBlocks - 1; ++i) {
3114 cbSingle.Put(entryBytes, [&](Maybe<ProfileBufferEntryWriter>& aEW) {
3115 MOZ_RELEASE_ASSERT(aEW.isSome());
3116 while (aEW->RemainingBytes() > 0) {
3117 **aEW = '0' + i;
3118 ++(*aEW);
3121 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
3122 cbSingle, 1, 1 + blockBytes * (i + 1), i + 1, 0, 0);
3125 // Write the last block so that it's too big (by 1 byte) to fit in the chunk,
3126 // this should fail.
3127 const ProfileChunkedBuffer::Length remainingBytesForLastBlock =
3128 bufferBytes - blockBytes * (testBlocks - 1);
3129 MOZ_RELEASE_ASSERT(ULEB128Size(remainingBytesForLastBlock) == 1,
3130 "This test assumes block sizes are small enough so that "
3131 "their ULEB128-encoded size is 1 byte");
3132 const ProfileChunkedBuffer::Length entryToFitRemainingBytes =
3133 remainingBytesForLastBlock - ULEB128Size(remainingBytesForLastBlock);
3134 cbSingle.Put(entryToFitRemainingBytes + 1,
3135 [&](Maybe<ProfileBufferEntryWriter>& aEW) {
3136 MOZ_RELEASE_ASSERT(aEW.isNothing());
3138 // The buffer state should not have changed, apart from the failed bytes.
3139 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
3140 cbSingle, 1, 1 + blockBytes * (testBlocks - 1), testBlocks - 1, 0,
3141 remainingBytesForLastBlock + 1);
3143 size_t read = 0;
3144 cbSingle.ReadEach([&](ProfileBufferEntryReader& aER) {
3145 MOZ_RELEASE_ASSERT(aER.RemainingBytes() == entryBytes);
3146 while (aER.RemainingBytes() > 0) {
3147 MOZ_RELEASE_ASSERT(*aER == '0' + read);
3148 ++aER;
3150 ++read;
3152 MOZ_RELEASE_ASSERT(read == testBlocks - 1);
3154 // ~Interlude~ Test AppendContent:
3155 // Create another ProfileChunkedBuffer that will use a
3156 // ProfileBufferChunkManagerWithLocalLimit, which will give away
3157 // ProfileBufferChunks that can contain 128 bytes, using up to 1KB of memory
3158 // (including usable 128 bytes and headers).
3159 constexpr size_t bufferMaxSize = 1024;
3160 ProfileBufferChunkManagerWithLocalLimit cmTarget(bufferMaxSize, chunkMinSize);
3161 ProfileChunkedBuffer cbTarget(ProfileChunkedBuffer::ThreadSafety::WithMutex,
3162 cmTarget);
3164 // It should start empty.
3165 cbTarget.ReadEach(
3166 [](ProfileBufferEntryReader&) { MOZ_RELEASE_ASSERT(false); });
3167 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cbTarget, 1, 1, 0, 0, 0);
3169 // Copy the contents from cbSingle to cbTarget.
3170 cbTarget.AppendContents(cbSingle);
3172 // And verify that we now have the same contents in cbTarget.
3173 read = 0;
3174 cbTarget.ReadEach([&](ProfileBufferEntryReader& aER) {
3175 MOZ_RELEASE_ASSERT(aER.RemainingBytes() == entryBytes);
3176 while (aER.RemainingBytes() > 0) {
3177 MOZ_RELEASE_ASSERT(*aER == '0' + read);
3178 ++aER;
3180 ++read;
3182 MOZ_RELEASE_ASSERT(read == testBlocks - 1);
3183 // The state should be the same as the source.
3184 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
3185 cbTarget, 1, 1 + blockBytes * (testBlocks - 1), testBlocks - 1, 0, 0);
3187 # ifdef DEBUG
3188 // cbSingle.Dump();
3189 // cbTarget.Dump();
3190 # endif
3192 // Because we failed to write a too-big chunk above, the chunk was marked
3193 // full, so that entries should be consistently rejected from now on.
3194 cbSingle.Put(1, [&](Maybe<ProfileBufferEntryWriter>& aEW) {
3195 MOZ_RELEASE_ASSERT(aEW.isNothing());
3197 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
3198 cbSingle, 1, 1 + blockBytes * ((testBlocks - 1)), testBlocks - 1, 0,
3199 remainingBytesForLastBlock + 1 + ULEB128Size(1u) + 1);
3201 // Clear the buffer before the next test.
3203 cbSingle.Clear();
3204 // Clear() should move the index to the next chunk range -- even if it's
3205 // really reusing the same chunk.
3206 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cbSingle, 1 + bufferBytes,
3207 1 + bufferBytes, 0, 0, 0);
3208 cbSingle.ReadEach(
3209 [&](ProfileBufferEntryReader& aER) { MOZ_RELEASE_ASSERT(false); });
3211 // Second buffer-filling test: Try to write a final entry that just fits at
3212 // the end of the chunk.
3214 // Write all but one block.
3215 for (size_t i = 0; i < testBlocks - 1; ++i) {
3216 cbSingle.Put(entryBytes, [&](Maybe<ProfileBufferEntryWriter>& aEW) {
3217 MOZ_RELEASE_ASSERT(aEW.isSome());
3218 while (aEW->RemainingBytes() > 0) {
3219 **aEW = 'a' + i;
3220 ++(*aEW);
3223 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
3224 cbSingle, 1 + bufferBytes, 1 + bufferBytes + blockBytes * (i + 1),
3225 i + 1, 0, 0);
3228 read = 0;
3229 cbSingle.ReadEach([&](ProfileBufferEntryReader& aER) {
3230 MOZ_RELEASE_ASSERT(aER.RemainingBytes() == entryBytes);
3231 while (aER.RemainingBytes() > 0) {
3232 MOZ_RELEASE_ASSERT(*aER == 'a' + read);
3233 ++aER;
3235 ++read;
3237 MOZ_RELEASE_ASSERT(read == testBlocks - 1);
3239 // Write the last block so that it fits exactly in the chunk.
3240 cbSingle.Put(entryToFitRemainingBytes,
3241 [&](Maybe<ProfileBufferEntryWriter>& aEW) {
3242 MOZ_RELEASE_ASSERT(aEW.isSome());
3243 while (aEW->RemainingBytes() > 0) {
3244 **aEW = 'a' + (testBlocks - 1);
3245 ++(*aEW);
3248 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
3249 cbSingle, 1 + bufferBytes, 1 + bufferBytes + blockBytes * testBlocks,
3250 testBlocks, 0, 0);
3252 read = 0;
3253 cbSingle.ReadEach([&](ProfileBufferEntryReader& aER) {
3254 MOZ_RELEASE_ASSERT(
3255 aER.RemainingBytes() ==
3256 ((read < testBlocks) ? entryBytes : entryToFitRemainingBytes));
3257 while (aER.RemainingBytes() > 0) {
3258 MOZ_RELEASE_ASSERT(*aER == 'a' + read);
3259 ++aER;
3261 ++read;
3263 MOZ_RELEASE_ASSERT(read == testBlocks);
3265 // Because the single chunk has been filled, it shouldn't be possible to write
3266 // more entries.
3267 cbSingle.Put(1, [&](Maybe<ProfileBufferEntryWriter>& aEW) {
3268 MOZ_RELEASE_ASSERT(aEW.isNothing());
3270 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
3271 cbSingle, 1 + bufferBytes, 1 + bufferBytes + blockBytes * testBlocks,
3272 testBlocks, 0, ULEB128Size(1u) + 1);
3274 cbSingle.Clear();
3275 // Clear() should move the index to the next chunk range -- even if it's
3276 // really reusing the same chunk.
3277 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(cbSingle, 1 + bufferBytes * 2,
3278 1 + bufferBytes * 2, 0, 0, 0);
3279 cbSingle.ReadEach(
3280 [&](ProfileBufferEntryReader& aER) { MOZ_RELEASE_ASSERT(false); });
3282 // Clear() recycles the released chunk, so we should be able to record new
3283 // entries.
3284 cbSingle.Put(entryBytes, [&](Maybe<ProfileBufferEntryWriter>& aEW) {
3285 MOZ_RELEASE_ASSERT(aEW.isSome());
3286 while (aEW->RemainingBytes() > 0) {
3287 **aEW = 'x';
3288 ++(*aEW);
3291 VERIFY_PCB_START_END_PUSHED_CLEARED_FAILED(
3292 cbSingle, 1 + bufferBytes * 2,
3293 1 + bufferBytes * 2 + ULEB128Size(entryBytes) + entryBytes, 1, 0, 0);
3294 read = 0;
3295 cbSingle.ReadEach([&](ProfileBufferEntryReader& aER) {
3296 MOZ_RELEASE_ASSERT(read == 0);
3297 MOZ_RELEASE_ASSERT(aER.RemainingBytes() == entryBytes);
3298 while (aER.RemainingBytes() > 0) {
3299 MOZ_RELEASE_ASSERT(*aER == 'x');
3300 ++aER;
3302 ++read;
3304 MOZ_RELEASE_ASSERT(read == 1);
3306 printf("TestChunkedBufferSingle done\n");
3309 static void TestModuloBuffer(ModuloBuffer<>& mb, uint32_t MBSize) {
3310 using MB = ModuloBuffer<>;
3312 MOZ_RELEASE_ASSERT(mb.BufferLength().Value() == MBSize);
3314 // Iterator comparisons.
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(2) < mb.ReaderAt(3));
3318 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) <= mb.ReaderAt(2));
3319 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) <= mb.ReaderAt(3));
3320 MOZ_RELEASE_ASSERT(mb.ReaderAt(3) > mb.ReaderAt(2));
3321 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) >= mb.ReaderAt(2));
3322 MOZ_RELEASE_ASSERT(mb.ReaderAt(3) >= mb.ReaderAt(2));
3324 // Iterators indices don't wrap around (even though they may be pointing at
3325 // the same location).
3326 MOZ_RELEASE_ASSERT(mb.ReaderAt(2) != mb.ReaderAt(MBSize + 2));
3327 MOZ_RELEASE_ASSERT(mb.ReaderAt(MBSize + 2) != mb.ReaderAt(2));
3329 // Dereference.
3330 static_assert(std::is_same<decltype(*mb.ReaderAt(0)), const MB::Byte&>::value,
3331 "Dereferencing from a reader should return const Byte*");
3332 static_assert(std::is_same<decltype(*mb.WriterAt(0)), MB::Byte&>::value,
3333 "Dereferencing from a writer should return Byte*");
3334 // Contiguous between 0 and MBSize-1.
3335 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize - 1) ==
3336 &*mb.ReaderAt(0) + (MBSize - 1));
3337 // Wraps around.
3338 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize) == &*mb.ReaderAt(0));
3339 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize + MBSize - 1) ==
3340 &*mb.ReaderAt(MBSize - 1));
3341 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize + MBSize) == &*mb.ReaderAt(0));
3342 // Power of 2 modulo wrapping.
3343 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(uint32_t(-1)) == &*mb.ReaderAt(MBSize - 1));
3344 MOZ_RELEASE_ASSERT(&*mb.ReaderAt(static_cast<MB::Index>(-1)) ==
3345 &*mb.ReaderAt(MBSize - 1));
3347 // Arithmetic.
3348 MB::Reader arit = mb.ReaderAt(0);
3349 MOZ_RELEASE_ASSERT(++arit == mb.ReaderAt(1));
3350 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1));
3352 MOZ_RELEASE_ASSERT(--arit == mb.ReaderAt(0));
3353 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0));
3355 MOZ_RELEASE_ASSERT(arit++ == mb.ReaderAt(0));
3356 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1));
3358 MOZ_RELEASE_ASSERT(arit-- == mb.ReaderAt(1));
3359 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0));
3361 MOZ_RELEASE_ASSERT(arit + 3 == mb.ReaderAt(3));
3362 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0));
3364 MOZ_RELEASE_ASSERT(4 + arit == mb.ReaderAt(4));
3365 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0));
3367 // (Can't have assignments inside asserts, hence the split.)
3368 const bool checkPlusEq = ((arit += 3) == mb.ReaderAt(3));
3369 MOZ_RELEASE_ASSERT(checkPlusEq);
3370 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(3));
3372 MOZ_RELEASE_ASSERT((arit - 2) == mb.ReaderAt(1));
3373 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(3));
3375 const bool checkMinusEq = ((arit -= 2) == mb.ReaderAt(1));
3376 MOZ_RELEASE_ASSERT(checkMinusEq);
3377 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1));
3379 // Random access.
3380 MOZ_RELEASE_ASSERT(&arit[3] == &*(arit + 3));
3381 MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1));
3383 // Iterator difference.
3384 MOZ_RELEASE_ASSERT(mb.ReaderAt(3) - mb.ReaderAt(1) == 2);
3385 MOZ_RELEASE_ASSERT(mb.ReaderAt(1) - mb.ReaderAt(3) == MB::Index(-2));
3387 // Only testing Writer, as Reader is just a subset with no code differences.
3388 MB::Writer it = mb.WriterAt(0);
3389 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 0);
3391 // Write two characters at the start.
3392 it.WriteObject('x');
3393 it.WriteObject('y');
3395 // Backtrack to read them.
3396 it -= 2;
3397 // PeekObject should read without moving.
3398 MOZ_RELEASE_ASSERT(it.PeekObject<char>() == 'x');
3399 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 0);
3400 // ReadObject should read and move past the character.
3401 MOZ_RELEASE_ASSERT(it.ReadObject<char>() == 'x');
3402 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 1);
3403 MOZ_RELEASE_ASSERT(it.PeekObject<char>() == 'y');
3404 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 1);
3405 MOZ_RELEASE_ASSERT(it.ReadObject<char>() == 'y');
3406 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 2);
3408 // Checking that a reader can be created from a writer.
3409 MB::Reader it2(it);
3410 MOZ_RELEASE_ASSERT(it2.CurrentIndex() == 2);
3411 // Or assigned.
3412 it2 = it;
3413 MOZ_RELEASE_ASSERT(it2.CurrentIndex() == 2);
3415 // Iterator traits.
3416 static_assert(std::is_same<std::iterator_traits<MB::Reader>::difference_type,
3417 MB::Index>::value,
3418 "ModuloBuffer::Reader::difference_type should be Index");
3419 static_assert(std::is_same<std::iterator_traits<MB::Reader>::value_type,
3420 MB::Byte>::value,
3421 "ModuloBuffer::Reader::value_type should be Byte");
3422 static_assert(std::is_same<std::iterator_traits<MB::Reader>::pointer,
3423 const MB::Byte*>::value,
3424 "ModuloBuffer::Reader::pointer should be const Byte*");
3425 static_assert(std::is_same<std::iterator_traits<MB::Reader>::reference,
3426 const MB::Byte&>::value,
3427 "ModuloBuffer::Reader::reference should be const Byte&");
3428 static_assert(std::is_base_of<
3429 std::input_iterator_tag,
3430 std::iterator_traits<MB::Reader>::iterator_category>::value,
3431 "ModuloBuffer::Reader::iterator_category should be derived "
3432 "from input_iterator_tag");
3433 static_assert(std::is_base_of<
3434 std::forward_iterator_tag,
3435 std::iterator_traits<MB::Reader>::iterator_category>::value,
3436 "ModuloBuffer::Reader::iterator_category should be derived "
3437 "from forward_iterator_tag");
3438 static_assert(std::is_base_of<
3439 std::bidirectional_iterator_tag,
3440 std::iterator_traits<MB::Reader>::iterator_category>::value,
3441 "ModuloBuffer::Reader::iterator_category should be derived "
3442 "from bidirectional_iterator_tag");
3443 static_assert(
3444 std::is_same<std::iterator_traits<MB::Reader>::iterator_category,
3445 std::random_access_iterator_tag>::value,
3446 "ModuloBuffer::Reader::iterator_category should be "
3447 "random_access_iterator_tag");
3449 // Use as input iterator by std::string constructor (which is only considered
3450 // with proper input iterators.)
3451 std::string s(mb.ReaderAt(0), mb.ReaderAt(2));
3452 MOZ_RELEASE_ASSERT(s == "xy");
3454 // Write 4-byte number at index 2.
3455 it.WriteObject(int32_t(123));
3456 MOZ_RELEASE_ASSERT(it.CurrentIndex() == 6);
3457 // And another, which should now wrap around (but index continues on.)
3458 it.WriteObject(int32_t(456));
3459 MOZ_RELEASE_ASSERT(it.CurrentIndex() == MBSize + 2);
3460 // Even though index==MBSize+2, we can read the object we wrote at 2.
3461 MOZ_RELEASE_ASSERT(it.ReadObject<int32_t>() == 123);
3462 MOZ_RELEASE_ASSERT(it.CurrentIndex() == MBSize + 6);
3463 // And similarly, index MBSize+6 points at the same location as index 6.
3464 MOZ_RELEASE_ASSERT(it.ReadObject<int32_t>() == 456);
3465 MOZ_RELEASE_ASSERT(it.CurrentIndex() == MBSize + MBSize + 2);
3468 void TestModuloBuffer() {
3469 printf("TestModuloBuffer...\n");
3471 // Testing ModuloBuffer with default template arguments.
3472 using MB = ModuloBuffer<>;
3474 // Only 8-byte buffers, to easily test wrap-around.
3475 constexpr uint32_t MBSize = 8;
3477 // MB with self-allocated heap buffer.
3478 MB mbByLength(MakePowerOfTwo32<MBSize>());
3479 TestModuloBuffer(mbByLength, MBSize);
3481 // MB taking ownership of a provided UniquePtr to a buffer.
3482 auto uniqueBuffer = MakeUnique<uint8_t[]>(MBSize);
3483 MB mbByUniquePtr(MakeUnique<uint8_t[]>(MBSize), MakePowerOfTwo32<MBSize>());
3484 TestModuloBuffer(mbByUniquePtr, MBSize);
3486 // MB using part of a buffer on the stack. The buffer is three times the
3487 // required size: The middle third is where ModuloBuffer will work, the first
3488 // and last thirds are only used to later verify that ModuloBuffer didn't go
3489 // out of its bounds.
3490 uint8_t buffer[MBSize * 3];
3491 // Pre-fill the buffer with a known pattern, so we can later see what changed.
3492 for (size_t i = 0; i < MBSize * 3; ++i) {
3493 buffer[i] = uint8_t('A' + i);
3495 MB mbByBuffer(&buffer[MBSize], MakePowerOfTwo32<MBSize>());
3496 TestModuloBuffer(mbByBuffer, MBSize);
3498 // Check that only the provided stack-based sub-buffer was modified.
3499 uint32_t changed = 0;
3500 for (size_t i = MBSize; i < MBSize * 2; ++i) {
3501 changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1;
3503 // Expect at least 75% changes.
3504 MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
3506 // Everything around the sub-buffer should be unchanged.
3507 for (size_t i = 0; i < MBSize; ++i) {
3508 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
3510 for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
3511 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
3514 // Check that move-construction is allowed. This verifies that we do not
3515 // crash from a double free, when `mbByBuffer` and `mbByStolenBuffer` are both
3516 // destroyed at the end of this function.
3517 MB mbByStolenBuffer = std::move(mbByBuffer);
3518 TestModuloBuffer(mbByStolenBuffer, MBSize);
3520 // Check that only the provided stack-based sub-buffer was modified.
3521 changed = 0;
3522 for (size_t i = MBSize; i < MBSize * 2; ++i) {
3523 changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1;
3525 // Expect at least 75% changes.
3526 MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
3528 // Everything around the sub-buffer should be unchanged.
3529 for (size_t i = 0; i < MBSize; ++i) {
3530 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
3532 for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
3533 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
3536 // This test function does a `ReadInto` as directed, and checks that the
3537 // result is the same as if the copy had been done manually byte-by-byte.
3538 // `TestReadInto(3, 7, 2)` copies from index 3 to index 7, 2 bytes long.
3539 // Return the output string (from `ReadInto`) for external checks.
3540 auto TestReadInto = [](MB::Index aReadFrom, MB::Index aWriteTo,
3541 MB::Length aBytes) {
3542 constexpr uint32_t TRISize = 16;
3544 // Prepare an input buffer, all different elements.
3545 uint8_t input[TRISize + 1] = "ABCDEFGHIJKLMNOP";
3546 const MB mbInput(input, MakePowerOfTwo32<TRISize>());
3548 // Prepare an output buffer, different from input.
3549 uint8_t output[TRISize + 1] = "abcdefghijklmnop";
3550 MB mbOutput(output, MakePowerOfTwo32<TRISize>());
3552 // Run ReadInto.
3553 auto writer = mbOutput.WriterAt(aWriteTo);
3554 mbInput.ReaderAt(aReadFrom).ReadInto(writer, aBytes);
3556 // Do the same operation manually.
3557 uint8_t outputCheck[TRISize + 1] = "abcdefghijklmnop";
3558 MB mbOutputCheck(outputCheck, MakePowerOfTwo32<TRISize>());
3559 auto readerCheck = mbInput.ReaderAt(aReadFrom);
3560 auto writerCheck = mbOutputCheck.WriterAt(aWriteTo);
3561 for (MB::Length i = 0; i < aBytes; ++i) {
3562 *writerCheck++ = *readerCheck++;
3565 // Compare the two outputs.
3566 for (uint32_t i = 0; i < TRISize; ++i) {
3567 # ifdef TEST_MODULOBUFFER_FAILURE_DEBUG
3568 // Only used when debugging failures.
3569 if (output[i] != outputCheck[i]) {
3570 printf(
3571 "*** from=%u to=%u bytes=%u i=%u\ninput: '%s'\noutput: "
3572 "'%s'\ncheck: '%s'\n",
3573 unsigned(aReadFrom), unsigned(aWriteTo), unsigned(aBytes),
3574 unsigned(i), input, output, outputCheck);
3576 # endif
3577 MOZ_RELEASE_ASSERT(output[i] == outputCheck[i]);
3580 # ifdef TEST_MODULOBUFFER_HELPER
3581 // Only used when adding more tests.
3582 printf("*** from=%u to=%u bytes=%u output: %s\n", unsigned(aReadFrom),
3583 unsigned(aWriteTo), unsigned(aBytes), output);
3584 # endif
3586 return std::string(reinterpret_cast<const char*>(output));
3589 // A few manual checks:
3590 constexpr uint32_t TRISize = 16;
3591 MOZ_RELEASE_ASSERT(TestReadInto(0, 0, 0) == "abcdefghijklmnop");
3592 MOZ_RELEASE_ASSERT(TestReadInto(0, 0, TRISize) == "ABCDEFGHIJKLMNOP");
3593 MOZ_RELEASE_ASSERT(TestReadInto(0, 5, TRISize) == "LMNOPABCDEFGHIJK");
3594 MOZ_RELEASE_ASSERT(TestReadInto(5, 0, TRISize) == "FGHIJKLMNOPABCDE");
3596 // Test everything! (16^3 = 4096, not too much.)
3597 for (MB::Index r = 0; r < TRISize; ++r) {
3598 for (MB::Index w = 0; w < TRISize; ++w) {
3599 for (MB::Length len = 0; len < TRISize; ++len) {
3600 TestReadInto(r, w, len);
3605 printf("TestModuloBuffer done\n");
3608 void TestBlocksRingBufferAPI() {
3609 printf("TestBlocksRingBufferAPI...\n");
3611 // Create a 16-byte buffer, enough to store up to 3 entries (1 byte size + 4
3612 // bytes uint64_t).
3613 constexpr uint32_t MBSize = 16;
3614 uint8_t buffer[MBSize * 3];
3615 for (size_t i = 0; i < MBSize * 3; ++i) {
3616 buffer[i] = uint8_t('A' + i);
3619 // Start a temporary block to constrain buffer lifetime.
3621 BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex,
3622 &buffer[MBSize], MakePowerOfTwo32<MBSize>());
3624 # define VERIFY_START_END_PUSHED_CLEARED(aStart, aEnd, aPushed, aCleared) \
3626 BlocksRingBuffer::State state = rb.GetState(); \
3627 MOZ_RELEASE_ASSERT(state.mRangeStart.ConvertToProfileBufferIndex() == \
3628 (aStart)); \
3629 MOZ_RELEASE_ASSERT(state.mRangeEnd.ConvertToProfileBufferIndex() == \
3630 (aEnd)); \
3631 MOZ_RELEASE_ASSERT(state.mPushedBlockCount == (aPushed)); \
3632 MOZ_RELEASE_ASSERT(state.mClearedBlockCount == (aCleared)); \
3635 // All entries will contain one 32-bit number. The resulting blocks will
3636 // have the following structure:
3637 // - 1 byte for the LEB128 size of 4
3638 // - 4 bytes for the number.
3639 // E.g., if we have entries with `123` and `456`:
3640 // .-- Index 0 reserved for empty ProfileBufferBlockIndex, nothing there.
3641 // | .-- first readable block at index 1
3642 // | |.-- first block at index 1
3643 // | ||.-- 1 byte for the entry size, which is `4` (32 bits)
3644 // | ||| .-- entry starts at index 2, contains 32-bit int
3645 // | ||| | .-- entry and block finish *after* index 5 (so 6)
3646 // | ||| | | .-- second block starts at index 6
3647 // | ||| | | | etc.
3648 // | ||| | | | .-- End readable blocks: 11
3649 // v vvv v v V v
3650 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
3651 // - S[4 | int(123) ] [4 | int(456) ]E
3653 // Empty buffer to start with.
3654 // Start&end indices still at 1 (0 is reserved for the default
3655 // ProfileBufferBlockIndex{} that cannot point at a valid entry), nothing
3656 // cleared.
3657 VERIFY_START_END_PUSHED_CLEARED(1, 1, 0, 0);
3659 // Default ProfileBufferBlockIndex.
3660 ProfileBufferBlockIndex bi0;
3661 if (bi0) {
3662 MOZ_RELEASE_ASSERT(false,
3663 "if (ProfileBufferBlockIndex{}) should fail test");
3665 if (!bi0) {
3666 } else {
3667 MOZ_RELEASE_ASSERT(false,
3668 "if (!ProfileBufferBlockIndex{}) should succeed test");
3670 MOZ_RELEASE_ASSERT(!bi0);
3671 MOZ_RELEASE_ASSERT(bi0 == bi0);
3672 MOZ_RELEASE_ASSERT(bi0 <= bi0);
3673 MOZ_RELEASE_ASSERT(bi0 >= bi0);
3674 MOZ_RELEASE_ASSERT(!(bi0 != bi0));
3675 MOZ_RELEASE_ASSERT(!(bi0 < bi0));
3676 MOZ_RELEASE_ASSERT(!(bi0 > bi0));
3678 // Default ProfileBufferBlockIndex can be used, but returns no valid entry.
3679 rb.ReadAt(bi0, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
3680 MOZ_RELEASE_ASSERT(aMaybeReader.isNothing());
3683 // Push `1` directly.
3684 MOZ_RELEASE_ASSERT(
3685 rb.PutObject(uint32_t(1)).ConvertToProfileBufferIndex() == 1);
3686 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
3687 // - S[4 | int(1) ]E
3688 VERIFY_START_END_PUSHED_CLEARED(1, 6, 1, 0);
3690 // Push `2` through ReserveAndPut, check output ProfileBufferBlockIndex.
3691 auto bi2 = rb.ReserveAndPut([]() { return sizeof(uint32_t); },
3692 [](Maybe<ProfileBufferEntryWriter>& aEW) {
3693 MOZ_RELEASE_ASSERT(aEW.isSome());
3694 aEW->WriteObject(uint32_t(2));
3695 return aEW->CurrentBlockIndex();
3697 static_assert(std::is_same<decltype(bi2), ProfileBufferBlockIndex>::value,
3698 "All index-returning functions should return a "
3699 "ProfileBufferBlockIndex");
3700 MOZ_RELEASE_ASSERT(bi2.ConvertToProfileBufferIndex() == 6);
3701 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
3702 // - S[4 | int(1) ] [4 | int(2) ]E
3703 VERIFY_START_END_PUSHED_CLEARED(1, 11, 2, 0);
3705 // Check single entry at bi2, store next block index.
3706 auto i2Next =
3707 rb.ReadAt(bi2, [bi2](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
3708 MOZ_RELEASE_ASSERT(aMaybeReader.isSome());
3709 MOZ_RELEASE_ASSERT(aMaybeReader->CurrentBlockIndex() == bi2);
3710 MOZ_RELEASE_ASSERT(aMaybeReader->NextBlockIndex() == nullptr);
3711 size_t entrySize = aMaybeReader->RemainingBytes();
3712 MOZ_RELEASE_ASSERT(aMaybeReader->ReadObject<uint32_t>() == 2);
3713 // The next block index is after this block, which is made of the
3714 // entry size (coded as ULEB128) followed by the entry itself.
3715 return bi2.ConvertToProfileBufferIndex() + ULEB128Size(entrySize) +
3716 entrySize;
3718 auto bi2Next = rb.GetState().mRangeEnd;
3719 MOZ_RELEASE_ASSERT(bi2Next.ConvertToProfileBufferIndex() == i2Next);
3720 // bi2Next is at the end, nothing to read.
3721 rb.ReadAt(bi2Next, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
3722 MOZ_RELEASE_ASSERT(aMaybeReader.isNothing());
3725 // ProfileBufferBlockIndex tests.
3726 if (bi2) {
3727 } else {
3728 MOZ_RELEASE_ASSERT(
3729 false,
3730 "if (non-default-ProfileBufferBlockIndex) should succeed test");
3732 if (!bi2) {
3733 MOZ_RELEASE_ASSERT(
3734 false, "if (!non-default-ProfileBufferBlockIndex) should fail test");
3737 MOZ_RELEASE_ASSERT(!!bi2);
3738 MOZ_RELEASE_ASSERT(bi2 == bi2);
3739 MOZ_RELEASE_ASSERT(bi2 <= bi2);
3740 MOZ_RELEASE_ASSERT(bi2 >= bi2);
3741 MOZ_RELEASE_ASSERT(!(bi2 != bi2));
3742 MOZ_RELEASE_ASSERT(!(bi2 < bi2));
3743 MOZ_RELEASE_ASSERT(!(bi2 > bi2));
3745 MOZ_RELEASE_ASSERT(bi0 != bi2);
3746 MOZ_RELEASE_ASSERT(bi0 < bi2);
3747 MOZ_RELEASE_ASSERT(bi0 <= bi2);
3748 MOZ_RELEASE_ASSERT(!(bi0 == bi2));
3749 MOZ_RELEASE_ASSERT(!(bi0 > bi2));
3750 MOZ_RELEASE_ASSERT(!(bi0 >= bi2));
3752 MOZ_RELEASE_ASSERT(bi2 != bi0);
3753 MOZ_RELEASE_ASSERT(bi2 > bi0);
3754 MOZ_RELEASE_ASSERT(bi2 >= bi0);
3755 MOZ_RELEASE_ASSERT(!(bi2 == bi0));
3756 MOZ_RELEASE_ASSERT(!(bi2 < bi0));
3757 MOZ_RELEASE_ASSERT(!(bi2 <= bi0));
3759 MOZ_RELEASE_ASSERT(bi2 != bi2Next);
3760 MOZ_RELEASE_ASSERT(bi2 < bi2Next);
3761 MOZ_RELEASE_ASSERT(bi2 <= bi2Next);
3762 MOZ_RELEASE_ASSERT(!(bi2 == bi2Next));
3763 MOZ_RELEASE_ASSERT(!(bi2 > bi2Next));
3764 MOZ_RELEASE_ASSERT(!(bi2 >= bi2Next));
3766 MOZ_RELEASE_ASSERT(bi2Next != bi2);
3767 MOZ_RELEASE_ASSERT(bi2Next > bi2);
3768 MOZ_RELEASE_ASSERT(bi2Next >= bi2);
3769 MOZ_RELEASE_ASSERT(!(bi2Next == bi2));
3770 MOZ_RELEASE_ASSERT(!(bi2Next < bi2));
3771 MOZ_RELEASE_ASSERT(!(bi2Next <= bi2));
3773 // Push `3` through Put, check writer output
3774 // is returned to the initial caller.
3775 auto put3 =
3776 rb.Put(sizeof(uint32_t), [&](Maybe<ProfileBufferEntryWriter>& aEW) {
3777 MOZ_RELEASE_ASSERT(aEW.isSome());
3778 aEW->WriteObject(uint32_t(3));
3779 MOZ_RELEASE_ASSERT(aEW->CurrentBlockIndex() == bi2Next);
3780 return float(aEW->CurrentBlockIndex().ConvertToProfileBufferIndex());
3782 static_assert(std::is_same<decltype(put3), float>::value,
3783 "Expect float as returned by callback.");
3784 MOZ_RELEASE_ASSERT(put3 == 11.0);
3785 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16)
3786 // - S[4 | int(1) ] [4 | int(2) ] [4 | int(3) ]E
3787 VERIFY_START_END_PUSHED_CLEARED(1, 16, 3, 0);
3789 // Re-Read single entry at bi2, it should now have a next entry.
3790 rb.ReadAt(bi2, [&](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
3791 MOZ_RELEASE_ASSERT(aMaybeReader.isSome());
3792 MOZ_RELEASE_ASSERT(aMaybeReader->CurrentBlockIndex() == bi2);
3793 MOZ_RELEASE_ASSERT(aMaybeReader->ReadObject<uint32_t>() == 2);
3794 MOZ_RELEASE_ASSERT(aMaybeReader->NextBlockIndex() == bi2Next);
3797 // Check that we have `1` to `3`.
3798 uint32_t count = 0;
3799 rb.ReadEach([&](ProfileBufferEntryReader& aReader) {
3800 MOZ_RELEASE_ASSERT(aReader.ReadObject<uint32_t>() == ++count);
3802 MOZ_RELEASE_ASSERT(count == 3);
3804 // Push `4`, store its ProfileBufferBlockIndex for later.
3805 // This will wrap around, and clear the first entry.
3806 ProfileBufferBlockIndex bi4 = rb.PutObject(uint32_t(4));
3807 // Before:
3808 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16)
3809 // - S[4 | int(1) ] [4 | int(2) ] [4 | int(3) ]E
3810 // 1. First entry cleared:
3811 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16)
3812 // - ? ? ? ? ? S[4 | int(2) ] [4 | int(3) ]E
3813 // 2. New entry starts at 15 and wraps around: (shown on separate line)
3814 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16)
3815 // - ? ? ? ? ? S[4 | int(2) ] [4 | int(3) ]
3816 // 16 17 18 19 20 21 ...
3817 // [4 | int(4) ]E
3818 // (collapsed)
3819 // 16 17 18 19 20 21 6 7 8 9 10 11 12 13 14 15 (16)
3820 // [4 | int(4) ]E ? S[4 | int(2) ] [4 | int(3) ]
3821 VERIFY_START_END_PUSHED_CLEARED(6, 21, 4, 1);
3823 // Check that we have `2` to `4`.
3824 count = 1;
3825 rb.ReadEach([&](ProfileBufferEntryReader& aReader) {
3826 MOZ_RELEASE_ASSERT(aReader.ReadObject<uint32_t>() == ++count);
3828 MOZ_RELEASE_ASSERT(count == 4);
3830 // Push 5 through Put, no returns.
3831 // This will clear the second entry.
3832 // Check that the EntryWriter can access bi4 but not bi2.
3833 auto bi5 =
3834 rb.Put(sizeof(uint32_t), [&](Maybe<ProfileBufferEntryWriter>& aEW) {
3835 MOZ_RELEASE_ASSERT(aEW.isSome());
3836 aEW->WriteObject(uint32_t(5));
3837 return aEW->CurrentBlockIndex();
3839 auto bi6 = rb.GetState().mRangeEnd;
3840 // 16 17 18 19 20 21 22 23 24 25 26 11 12 13 14 15 (16)
3841 // [4 | int(4) ] [4 | int(5) ]E ? S[4 | int(3) ]
3842 VERIFY_START_END_PUSHED_CLEARED(11, 26, 5, 2);
3844 // Read single entry at bi2, should now gracefully fail.
3845 rb.ReadAt(bi2, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
3846 MOZ_RELEASE_ASSERT(aMaybeReader.isNothing());
3849 // Read single entry at bi5.
3850 rb.ReadAt(bi5, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
3851 MOZ_RELEASE_ASSERT(aMaybeReader.isSome());
3852 MOZ_RELEASE_ASSERT(aMaybeReader->ReadObject<uint32_t>() == 5);
3855 rb.Read([&](BlocksRingBuffer::Reader* aReader) {
3856 MOZ_RELEASE_ASSERT(!!aReader);
3857 // begin() and end() should be at the range edges (verified above).
3858 MOZ_RELEASE_ASSERT(
3859 aReader->begin().CurrentBlockIndex().ConvertToProfileBufferIndex() ==
3860 11);
3861 MOZ_RELEASE_ASSERT(
3862 aReader->end().CurrentBlockIndex().ConvertToProfileBufferIndex() ==
3863 26);
3864 // Null ProfileBufferBlockIndex clamped to the beginning.
3865 MOZ_RELEASE_ASSERT(aReader->At(bi0) == aReader->begin());
3866 // Cleared block index clamped to the beginning.
3867 MOZ_RELEASE_ASSERT(aReader->At(bi2) == aReader->begin());
3868 // At(begin) same as begin().
3869 MOZ_RELEASE_ASSERT(aReader->At(aReader->begin().CurrentBlockIndex()) ==
3870 aReader->begin());
3871 // bi5 at expected position.
3872 MOZ_RELEASE_ASSERT(
3873 aReader->At(bi5).CurrentBlockIndex().ConvertToProfileBufferIndex() ==
3874 21);
3875 // bi6 at expected position at the end.
3876 MOZ_RELEASE_ASSERT(aReader->At(bi6) == aReader->end());
3877 // At(end) same as end().
3878 MOZ_RELEASE_ASSERT(aReader->At(aReader->end().CurrentBlockIndex()) ==
3879 aReader->end());
3882 // Check that we have `3` to `5`.
3883 count = 2;
3884 rb.ReadEach([&](ProfileBufferEntryReader& aReader) {
3885 MOZ_RELEASE_ASSERT(aReader.ReadObject<uint32_t>() == ++count);
3887 MOZ_RELEASE_ASSERT(count == 5);
3889 // Clear everything before `4`, this should clear `3`.
3890 rb.ClearBefore(bi4);
3891 // 16 17 18 19 20 21 22 23 24 25 26 11 12 13 14 15
3892 // S[4 | int(4) ] [4 | int(5) ]E ? ? ? ? ? ?
3893 VERIFY_START_END_PUSHED_CLEARED(16, 26, 5, 3);
3895 // Check that we have `4` to `5`.
3896 count = 3;
3897 rb.ReadEach([&](ProfileBufferEntryReader& aReader) {
3898 MOZ_RELEASE_ASSERT(aReader.ReadObject<uint32_t>() == ++count);
3900 MOZ_RELEASE_ASSERT(count == 5);
3902 // Clear everything before `4` again, nothing to clear.
3903 rb.ClearBefore(bi4);
3904 VERIFY_START_END_PUSHED_CLEARED(16, 26, 5, 3);
3906 // Clear everything, this should clear `4` and `5`, and bring the start
3907 // index where the end index currently is.
3908 rb.ClearBefore(bi6);
3909 // 16 17 18 19 20 21 22 23 24 25 26 11 12 13 14 15
3910 // ? ? ? ? ? ? ? ? ? ? SE? ? ? ? ? ?
3911 VERIFY_START_END_PUSHED_CLEARED(26, 26, 5, 5);
3913 // Check that we have nothing to read.
3914 rb.ReadEach([&](auto&&) { MOZ_RELEASE_ASSERT(false); });
3916 // Read single entry at bi5, should now gracefully fail.
3917 rb.ReadAt(bi5, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
3918 MOZ_RELEASE_ASSERT(aMaybeReader.isNothing());
3921 // Clear everything before now-cleared `4`, nothing to clear.
3922 rb.ClearBefore(bi4);
3923 VERIFY_START_END_PUSHED_CLEARED(26, 26, 5, 5);
3925 // Push `6` directly.
3926 MOZ_RELEASE_ASSERT(rb.PutObject(uint32_t(6)) == bi6);
3927 // 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
3928 // ? ? ? ? ? ? ? ? ? ? S[4 | int(6) ]E ?
3929 VERIFY_START_END_PUSHED_CLEARED(26, 31, 6, 5);
3932 // Create a 2nd buffer and fill it with `7` and `8`.
3933 uint8_t buffer2[MBSize];
3934 BlocksRingBuffer rb2(BlocksRingBuffer::ThreadSafety::WithoutMutex,
3935 buffer2, MakePowerOfTwo32<MBSize>());
3936 rb2.PutObject(uint32_t(7));
3937 rb2.PutObject(uint32_t(8));
3938 // Main buffer shouldn't have changed.
3939 VERIFY_START_END_PUSHED_CLEARED(26, 31, 6, 5);
3941 // Append contents of rb2 to rb, this should end up being the same as
3942 // pushing the two numbers.
3943 rb.AppendContents(rb2);
3944 // 32 33 34 35 36 37 38 39 40 41 26 27 28 29 30 31
3945 // int(7) ] [4 | int(8) ]E ? S[4 | int(6) ] [4 |
3946 VERIFY_START_END_PUSHED_CLEARED(26, 41, 8, 5);
3948 // Append contents of rb2 to rb again, to verify that rb2 was not modified
3949 // above. This should clear `6` and the first `7`.
3950 rb.AppendContents(rb2);
3951 // 48 49 50 51 36 37 38 39 40 41 42 43 44 45 46 47
3952 // int(8) ]E ? S[4 | int(8) ] [4 | int(7) ] [4 |
3953 VERIFY_START_END_PUSHED_CLEARED(36, 51, 10, 7);
3955 // End of block where rb2 lives, to verify that it is not needed anymore
3956 // for its copied values to survive in rb.
3958 VERIFY_START_END_PUSHED_CLEARED(36, 51, 10, 7);
3960 // bi6 should now have been cleared.
3961 rb.ReadAt(bi6, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
3962 MOZ_RELEASE_ASSERT(aMaybeReader.isNothing());
3965 // Check that we have `8`, `7`, `8`.
3966 count = 0;
3967 uint32_t expected[3] = {8, 7, 8};
3968 rb.ReadEach([&](ProfileBufferEntryReader& aReader) {
3969 MOZ_RELEASE_ASSERT(count < 3);
3970 MOZ_RELEASE_ASSERT(aReader.ReadObject<uint32_t>() == expected[count++]);
3972 MOZ_RELEASE_ASSERT(count == 3);
3974 // End of block where rb lives, BlocksRingBuffer destructor should call
3975 // entry destructor for remaining entries.
3978 // Check that only the provided stack-based sub-buffer was modified.
3979 uint32_t changed = 0;
3980 for (size_t i = MBSize; i < MBSize * 2; ++i) {
3981 changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1;
3983 // Expect at least 75% changes.
3984 MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
3986 // Everything around the sub-buffer should be unchanged.
3987 for (size_t i = 0; i < MBSize; ++i) {
3988 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
3990 for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
3991 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
3994 printf("TestBlocksRingBufferAPI done\n");
3997 void TestBlocksRingBufferUnderlyingBufferChanges() {
3998 printf("TestBlocksRingBufferUnderlyingBufferChanges...\n");
4000 // Out-of-session BlocksRingBuffer to start with.
4001 BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex);
4003 // Block index to read at. Initially "null", but may be changed below.
4004 ProfileBufferBlockIndex bi;
4006 // Test all rb APIs when rb is out-of-session and therefore doesn't have an
4007 // underlying buffer.
4008 auto testOutOfSession = [&]() {
4009 MOZ_RELEASE_ASSERT(rb.BufferLength().isNothing());
4010 BlocksRingBuffer::State state = rb.GetState();
4011 // When out-of-session, range start and ends are the same, and there are no
4012 // pushed&cleared blocks.
4013 MOZ_RELEASE_ASSERT(state.mRangeStart == state.mRangeEnd);
4014 MOZ_RELEASE_ASSERT(state.mPushedBlockCount == 0);
4015 MOZ_RELEASE_ASSERT(state.mClearedBlockCount == 0);
4016 // `Put()` functions run the callback with `Nothing`.
4017 int32_t ran = 0;
4018 rb.Put(1, [&](Maybe<ProfileBufferEntryWriter>& aMaybeEntryWriter) {
4019 MOZ_RELEASE_ASSERT(aMaybeEntryWriter.isNothing());
4020 ++ran;
4022 MOZ_RELEASE_ASSERT(ran == 1);
4023 // `PutFrom` won't do anything, and returns the null
4024 // ProfileBufferBlockIndex.
4025 MOZ_RELEASE_ASSERT(rb.PutFrom(&ran, sizeof(ran)) ==
4026 ProfileBufferBlockIndex{});
4027 MOZ_RELEASE_ASSERT(rb.PutObject(ran) == ProfileBufferBlockIndex{});
4028 // `Read()` functions run the callback with `Nothing`.
4029 ran = 0;
4030 rb.Read([&](BlocksRingBuffer::Reader* aReader) {
4031 MOZ_RELEASE_ASSERT(!aReader);
4032 ++ran;
4034 MOZ_RELEASE_ASSERT(ran == 1);
4035 ran = 0;
4036 rb.ReadAt(ProfileBufferBlockIndex{},
4037 [&](Maybe<ProfileBufferEntryReader>&& aMaybeEntryReader) {
4038 MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing());
4039 ++ran;
4041 MOZ_RELEASE_ASSERT(ran == 1);
4042 ran = 0;
4043 rb.ReadAt(bi, [&](Maybe<ProfileBufferEntryReader>&& aMaybeEntryReader) {
4044 MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing());
4045 ++ran;
4047 MOZ_RELEASE_ASSERT(ran == 1);
4048 // `ReadEach` shouldn't run the callback (nothing to read).
4049 rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); });
4052 // As `testOutOfSession()` attempts to modify the buffer, we run it twice to
4053 // make sure one run doesn't influence the next one.
4054 testOutOfSession();
4055 testOutOfSession();
4057 rb.ClearBefore(bi);
4058 testOutOfSession();
4059 testOutOfSession();
4061 rb.Clear();
4062 testOutOfSession();
4063 testOutOfSession();
4065 rb.Reset();
4066 testOutOfSession();
4067 testOutOfSession();
4069 constexpr uint32_t MBSize = 32;
4071 rb.Set(MakePowerOfTwo<BlocksRingBuffer::Length, MBSize>());
4073 constexpr bool EMPTY = true;
4074 constexpr bool NOT_EMPTY = false;
4075 // Test all rb APIs when rb has an underlying buffer.
4076 auto testInSession = [&](bool aExpectEmpty) {
4077 MOZ_RELEASE_ASSERT(rb.BufferLength().isSome());
4078 BlocksRingBuffer::State state = rb.GetState();
4079 if (aExpectEmpty) {
4080 MOZ_RELEASE_ASSERT(state.mRangeStart == state.mRangeEnd);
4081 MOZ_RELEASE_ASSERT(state.mPushedBlockCount == 0);
4082 MOZ_RELEASE_ASSERT(state.mClearedBlockCount == 0);
4083 } else {
4084 MOZ_RELEASE_ASSERT(state.mRangeStart < state.mRangeEnd);
4085 MOZ_RELEASE_ASSERT(state.mPushedBlockCount > 0);
4086 MOZ_RELEASE_ASSERT(state.mClearedBlockCount <= state.mPushedBlockCount);
4088 int32_t ran = 0;
4089 // The following three `Put...` will write three int32_t of value 1.
4090 bi = rb.Put(sizeof(ran),
4091 [&](Maybe<ProfileBufferEntryWriter>& aMaybeEntryWriter) {
4092 MOZ_RELEASE_ASSERT(aMaybeEntryWriter.isSome());
4093 ++ran;
4094 aMaybeEntryWriter->WriteObject(ran);
4095 return aMaybeEntryWriter->CurrentBlockIndex();
4097 MOZ_RELEASE_ASSERT(ran == 1);
4098 MOZ_RELEASE_ASSERT(rb.PutFrom(&ran, sizeof(ran)) !=
4099 ProfileBufferBlockIndex{});
4100 MOZ_RELEASE_ASSERT(rb.PutObject(ran) != ProfileBufferBlockIndex{});
4101 ran = 0;
4102 rb.Read([&](BlocksRingBuffer::Reader* aReader) {
4103 MOZ_RELEASE_ASSERT(!!aReader);
4104 ++ran;
4106 MOZ_RELEASE_ASSERT(ran == 1);
4107 ran = 0;
4108 rb.ReadEach([&](ProfileBufferEntryReader& aEntryReader) {
4109 MOZ_RELEASE_ASSERT(aEntryReader.RemainingBytes() == sizeof(ran));
4110 MOZ_RELEASE_ASSERT(aEntryReader.ReadObject<decltype(ran)>() == 1);
4111 ++ran;
4113 MOZ_RELEASE_ASSERT(ran >= 3);
4114 ran = 0;
4115 rb.ReadAt(ProfileBufferBlockIndex{},
4116 [&](Maybe<ProfileBufferEntryReader>&& aMaybeEntryReader) {
4117 MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing());
4118 ++ran;
4120 MOZ_RELEASE_ASSERT(ran == 1);
4121 ran = 0;
4122 rb.ReadAt(bi, [&](Maybe<ProfileBufferEntryReader>&& aMaybeEntryReader) {
4123 MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing() == !bi);
4124 ++ran;
4126 MOZ_RELEASE_ASSERT(ran == 1);
4129 testInSession(EMPTY);
4130 testInSession(NOT_EMPTY);
4132 rb.Set(MakePowerOfTwo<BlocksRingBuffer::Length, 32>());
4133 MOZ_RELEASE_ASSERT(rb.BufferLength().isSome());
4134 rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); });
4136 testInSession(EMPTY);
4137 testInSession(NOT_EMPTY);
4139 rb.Reset();
4140 testOutOfSession();
4141 testOutOfSession();
4143 uint8_t buffer[MBSize * 3];
4144 for (size_t i = 0; i < MBSize * 3; ++i) {
4145 buffer[i] = uint8_t('A' + i);
4148 rb.Set(&buffer[MBSize], MakePowerOfTwo<BlocksRingBuffer::Length, MBSize>());
4149 MOZ_RELEASE_ASSERT(rb.BufferLength().isSome());
4150 rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); });
4152 testInSession(EMPTY);
4153 testInSession(NOT_EMPTY);
4155 rb.Reset();
4156 testOutOfSession();
4157 testOutOfSession();
4159 rb.Set(&buffer[MBSize], MakePowerOfTwo<BlocksRingBuffer::Length, MBSize>());
4160 MOZ_RELEASE_ASSERT(rb.BufferLength().isSome());
4161 rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); });
4163 testInSession(EMPTY);
4164 testInSession(NOT_EMPTY);
4166 // Remove the current underlying buffer, this should clear all entries.
4167 rb.Reset();
4169 // Check that only the provided stack-based sub-buffer was modified.
4170 uint32_t changed = 0;
4171 for (size_t i = MBSize; i < MBSize * 2; ++i) {
4172 changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1;
4174 // Expect at least 75% changes.
4175 MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
4177 // Everything around the sub-buffer should be unchanged.
4178 for (size_t i = 0; i < MBSize; ++i) {
4179 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
4181 for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
4182 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
4185 testOutOfSession();
4186 testOutOfSession();
4188 printf("TestBlocksRingBufferUnderlyingBufferChanges done\n");
4191 void TestBlocksRingBufferThreading() {
4192 printf("TestBlocksRingBufferThreading...\n");
4194 constexpr uint32_t MBSize = 8192;
4195 uint8_t buffer[MBSize * 3];
4196 for (size_t i = 0; i < MBSize * 3; ++i) {
4197 buffer[i] = uint8_t('A' + i);
4199 BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex,
4200 &buffer[MBSize], MakePowerOfTwo32<MBSize>());
4202 // Start reader thread.
4203 std::atomic<bool> stopReader{false};
4204 std::thread reader([&]() {
4205 for (;;) {
4206 BlocksRingBuffer::State state = rb.GetState();
4207 printf(
4208 "Reader: range=%llu..%llu (%llu bytes) pushed=%llu cleared=%llu "
4209 "(alive=%llu)\n",
4210 static_cast<unsigned long long>(
4211 state.mRangeStart.ConvertToProfileBufferIndex()),
4212 static_cast<unsigned long long>(
4213 state.mRangeEnd.ConvertToProfileBufferIndex()),
4214 static_cast<unsigned long long>(
4215 state.mRangeEnd.ConvertToProfileBufferIndex()) -
4216 static_cast<unsigned long long>(
4217 state.mRangeStart.ConvertToProfileBufferIndex()),
4218 static_cast<unsigned long long>(state.mPushedBlockCount),
4219 static_cast<unsigned long long>(state.mClearedBlockCount),
4220 static_cast<unsigned long long>(state.mPushedBlockCount -
4221 state.mClearedBlockCount));
4222 if (stopReader) {
4223 break;
4225 ::SleepMilli(1);
4229 // Start writer threads.
4230 constexpr int ThreadCount = 32;
4231 std::thread threads[ThreadCount];
4232 for (int threadNo = 0; threadNo < ThreadCount; ++threadNo) {
4233 threads[threadNo] = std::thread(
4234 [&](int aThreadNo) {
4235 ::SleepMilli(1);
4236 constexpr int pushCount = 1024;
4237 for (int push = 0; push < pushCount; ++push) {
4238 // Reserve as many bytes as the thread number (but at least enough
4239 // to store an int), and write an increasing int.
4240 rb.Put(std::max(aThreadNo, int(sizeof(push))),
4241 [&](Maybe<ProfileBufferEntryWriter>& aEW) {
4242 MOZ_RELEASE_ASSERT(aEW.isSome());
4243 aEW->WriteObject(aThreadNo * 1000000 + push);
4244 *aEW += aEW->RemainingBytes();
4248 threadNo);
4251 // Wait for all writer threads to die.
4252 for (auto&& thread : threads) {
4253 thread.join();
4256 // Stop reader thread.
4257 stopReader = true;
4258 reader.join();
4260 // Check that only the provided stack-based sub-buffer was modified.
4261 uint32_t changed = 0;
4262 for (size_t i = MBSize; i < MBSize * 2; ++i) {
4263 changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1;
4265 // Expect at least 75% changes.
4266 MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
4268 // Everything around the sub-buffer should be unchanged.
4269 for (size_t i = 0; i < MBSize; ++i) {
4270 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
4272 for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
4273 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
4276 printf("TestBlocksRingBufferThreading done\n");
4279 void TestBlocksRingBufferSerialization() {
4280 printf("TestBlocksRingBufferSerialization...\n");
4282 constexpr uint32_t MBSize = 64;
4283 uint8_t buffer[MBSize * 3];
4284 for (size_t i = 0; i < MBSize * 3; ++i) {
4285 buffer[i] = uint8_t('A' + i);
4287 BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex,
4288 &buffer[MBSize], MakePowerOfTwo32<MBSize>());
4290 // Will expect literal string to always have the same address.
4291 # define THE_ANSWER "The answer is "
4292 const char* theAnswer = THE_ANSWER;
4294 rb.PutObjects('0', WrapProfileBufferLiteralCStringPointer(THE_ANSWER), 42,
4295 std::string(" but pi="), 3.14);
4296 rb.ReadEach([&](ProfileBufferEntryReader& aER) {
4297 char c0;
4298 const char* answer;
4299 int integer;
4300 std::string str;
4301 double pi;
4302 aER.ReadIntoObjects(c0, answer, integer, str, pi);
4303 MOZ_RELEASE_ASSERT(c0 == '0');
4304 MOZ_RELEASE_ASSERT(answer == theAnswer);
4305 MOZ_RELEASE_ASSERT(integer == 42);
4306 MOZ_RELEASE_ASSERT(str == " but pi=");
4307 MOZ_RELEASE_ASSERT(pi == 3.14);
4309 rb.ReadEach([&](ProfileBufferEntryReader& aER) {
4310 char c0 = aER.ReadObject<char>();
4311 MOZ_RELEASE_ASSERT(c0 == '0');
4312 const char* answer = aER.ReadObject<const char*>();
4313 MOZ_RELEASE_ASSERT(answer == theAnswer);
4314 int integer = aER.ReadObject<int>();
4315 MOZ_RELEASE_ASSERT(integer == 42);
4316 std::string str = aER.ReadObject<std::string>();
4317 MOZ_RELEASE_ASSERT(str == " but pi=");
4318 double pi = aER.ReadObject<double>();
4319 MOZ_RELEASE_ASSERT(pi == 3.14);
4322 rb.Clear();
4323 // Write an int and store its ProfileBufferBlockIndex.
4324 ProfileBufferBlockIndex blockIndex = rb.PutObject(123);
4325 // It should be non-0.
4326 MOZ_RELEASE_ASSERT(blockIndex != ProfileBufferBlockIndex{});
4327 // Write that ProfileBufferBlockIndex.
4328 rb.PutObject(blockIndex);
4329 rb.Read([&](BlocksRingBuffer::Reader* aR) {
4330 BlocksRingBuffer::BlockIterator it = aR->begin();
4331 const BlocksRingBuffer::BlockIterator itEnd = aR->end();
4332 MOZ_RELEASE_ASSERT(it != itEnd);
4333 MOZ_RELEASE_ASSERT((*it).ReadObject<int>() == 123);
4334 ++it;
4335 MOZ_RELEASE_ASSERT(it != itEnd);
4336 MOZ_RELEASE_ASSERT((*it).ReadObject<ProfileBufferBlockIndex>() ==
4337 blockIndex);
4338 ++it;
4339 MOZ_RELEASE_ASSERT(it == itEnd);
4342 rb.Clear();
4343 rb.PutObjects(
4344 std::make_tuple('0', WrapProfileBufferLiteralCStringPointer(THE_ANSWER),
4345 42, std::string(" but pi="), 3.14));
4346 rb.ReadEach([&](ProfileBufferEntryReader& aER) {
4347 MOZ_RELEASE_ASSERT(aER.ReadObject<char>() == '0');
4348 MOZ_RELEASE_ASSERT(aER.ReadObject<const char*>() == theAnswer);
4349 MOZ_RELEASE_ASSERT(aER.ReadObject<int>() == 42);
4350 MOZ_RELEASE_ASSERT(aER.ReadObject<std::string>() == " but pi=");
4351 MOZ_RELEASE_ASSERT(aER.ReadObject<double>() == 3.14);
4354 rb.Clear();
4355 rb.PutObjects(
4356 std::make_tuple('0', WrapProfileBufferLiteralCStringPointer(THE_ANSWER),
4357 42, std::string(" but pi="), 3.14));
4358 rb.ReadEach([&](ProfileBufferEntryReader& aER) {
4359 MOZ_RELEASE_ASSERT(aER.ReadObject<char>() == '0');
4360 MOZ_RELEASE_ASSERT(aER.ReadObject<const char*>() == theAnswer);
4361 MOZ_RELEASE_ASSERT(aER.ReadObject<int>() == 42);
4362 MOZ_RELEASE_ASSERT(aER.ReadObject<std::string>() == " but pi=");
4363 MOZ_RELEASE_ASSERT(aER.ReadObject<double>() == 3.14);
4366 rb.Clear();
4368 UniqueFreePtr<char> ufps(strdup(THE_ANSWER));
4369 rb.PutObjects(ufps);
4371 rb.ReadEach([&](ProfileBufferEntryReader& aER) {
4372 auto ufps = aER.ReadObject<UniqueFreePtr<char>>();
4373 MOZ_RELEASE_ASSERT(!!ufps);
4374 MOZ_RELEASE_ASSERT(std::string(THE_ANSWER) == ufps.get());
4377 rb.Clear();
4378 int intArray[] = {1, 2, 3, 4, 5};
4379 rb.PutObjects(Span(intArray));
4380 rb.ReadEach([&](ProfileBufferEntryReader& aER) {
4381 int intArrayOut[sizeof(intArray) / sizeof(intArray[0])] = {0};
4382 auto outSpan = Span(intArrayOut);
4383 aER.ReadIntoObject(outSpan);
4384 for (size_t i = 0; i < sizeof(intArray) / sizeof(intArray[0]); ++i) {
4385 MOZ_RELEASE_ASSERT(intArrayOut[i] == intArray[i]);
4389 rb.Clear();
4390 rb.PutObjects(Maybe<int>(Nothing{}), Maybe<int>(Some(123)));
4391 rb.ReadEach([&](ProfileBufferEntryReader& aER) {
4392 Maybe<int> mi0, mi1;
4393 aER.ReadIntoObjects(mi0, mi1);
4394 MOZ_RELEASE_ASSERT(mi0.isNothing());
4395 MOZ_RELEASE_ASSERT(mi1.isSome());
4396 MOZ_RELEASE_ASSERT(*mi1 == 123);
4399 rb.Clear();
4400 using V = Variant<int, double, int>;
4401 V v0(VariantIndex<0>{}, 123);
4402 V v1(3.14);
4403 V v2(VariantIndex<2>{}, 456);
4404 rb.PutObjects(v0, v1, v2);
4405 rb.ReadEach([&](ProfileBufferEntryReader& aER) {
4406 MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v0);
4407 MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v1);
4408 MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v2);
4411 // 2nd BlocksRingBuffer to contain the 1st one. It has be be more than twice
4412 // the size.
4413 constexpr uint32_t MBSize2 = MBSize * 4;
4414 uint8_t buffer2[MBSize2 * 3];
4415 for (size_t i = 0; i < MBSize2 * 3; ++i) {
4416 buffer2[i] = uint8_t('B' + i);
4418 BlocksRingBuffer rb2(BlocksRingBuffer::ThreadSafety::WithoutMutex,
4419 &buffer2[MBSize2], MakePowerOfTwo32<MBSize2>());
4420 rb2.PutObject(rb);
4422 // 3rd BlocksRingBuffer deserialized from the 2nd one.
4423 uint8_t buffer3[MBSize * 3];
4424 for (size_t i = 0; i < MBSize * 3; ++i) {
4425 buffer3[i] = uint8_t('C' + i);
4427 BlocksRingBuffer rb3(BlocksRingBuffer::ThreadSafety::WithoutMutex,
4428 &buffer3[MBSize], MakePowerOfTwo32<MBSize>());
4429 rb2.ReadEach([&](ProfileBufferEntryReader& aER) { aER.ReadIntoObject(rb3); });
4431 // And a 4th heap-allocated one.
4432 UniquePtr<BlocksRingBuffer> rb4up;
4433 rb2.ReadEach([&](ProfileBufferEntryReader& aER) {
4434 rb4up = aER.ReadObject<UniquePtr<BlocksRingBuffer>>();
4436 MOZ_RELEASE_ASSERT(!!rb4up);
4438 // Clear 1st and 2nd BlocksRingBuffers, to ensure we have made a deep copy
4439 // into the 3rd&4th ones.
4440 rb.Clear();
4441 rb2.Clear();
4443 // And now the 3rd one should have the same contents as the 1st one had.
4444 rb3.ReadEach([&](ProfileBufferEntryReader& aER) {
4445 MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v0);
4446 MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v1);
4447 MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v2);
4450 // And 4th.
4451 rb4up->ReadEach([&](ProfileBufferEntryReader& aER) {
4452 MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v0);
4453 MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v1);
4454 MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v2);
4457 // In fact, the 3rd and 4th ones should have the same state, because they were
4458 // created the same way.
4459 MOZ_RELEASE_ASSERT(rb3.GetState().mRangeStart ==
4460 rb4up->GetState().mRangeStart);
4461 MOZ_RELEASE_ASSERT(rb3.GetState().mRangeEnd == rb4up->GetState().mRangeEnd);
4462 MOZ_RELEASE_ASSERT(rb3.GetState().mPushedBlockCount ==
4463 rb4up->GetState().mPushedBlockCount);
4464 MOZ_RELEASE_ASSERT(rb3.GetState().mClearedBlockCount ==
4465 rb4up->GetState().mClearedBlockCount);
4467 // Check that only the provided stack-based sub-buffer was modified.
4468 uint32_t changed = 0;
4469 for (size_t i = MBSize; i < MBSize * 2; ++i) {
4470 changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1;
4472 // Expect at least 75% changes.
4473 MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
4475 // Everything around the sub-buffers should be unchanged.
4476 for (size_t i = 0; i < MBSize; ++i) {
4477 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
4479 for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
4480 MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
4483 for (size_t i = 0; i < MBSize2; ++i) {
4484 MOZ_RELEASE_ASSERT(buffer2[i] == uint8_t('B' + i));
4486 for (size_t i = MBSize2 * 2; i < MBSize2 * 3; ++i) {
4487 MOZ_RELEASE_ASSERT(buffer2[i] == uint8_t('B' + i));
4490 for (size_t i = 0; i < MBSize; ++i) {
4491 MOZ_RELEASE_ASSERT(buffer3[i] == uint8_t('C' + i));
4493 for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
4494 MOZ_RELEASE_ASSERT(buffer3[i] == uint8_t('C' + i));
4497 printf("TestBlocksRingBufferSerialization done\n");
4500 void TestLiteralEmptyStringView() {
4501 printf("TestLiteralEmptyStringView...\n");
4503 static_assert(mozilla::LiteralEmptyStringView<char>() ==
4504 std::string_view(""));
4505 static_assert(!!mozilla::LiteralEmptyStringView<char>().data());
4506 static_assert(mozilla::LiteralEmptyStringView<char>().length() == 0);
4508 static_assert(mozilla::LiteralEmptyStringView<char16_t>() ==
4509 std::basic_string_view<char16_t>(u""));
4510 static_assert(!!mozilla::LiteralEmptyStringView<char16_t>().data());
4511 static_assert(mozilla::LiteralEmptyStringView<char16_t>().length() == 0);
4513 printf("TestLiteralEmptyStringView done\n");
4516 template <typename CHAR>
4517 void TestProfilerStringView() {
4518 if constexpr (std::is_same_v<CHAR, char>) {
4519 printf("TestProfilerStringView<char>...\n");
4520 } else if constexpr (std::is_same_v<CHAR, char16_t>) {
4521 printf("TestProfilerStringView<char16_t>...\n");
4522 } else {
4523 MOZ_RELEASE_ASSERT(false,
4524 "TestProfilerStringView only handles char and char16_t");
4527 // Used to verify implicit constructions, as this will normally be used in
4528 // function parameters.
4529 auto BSV = [](mozilla::ProfilerStringView<CHAR>&& aBSV) {
4530 return std::move(aBSV);
4533 // These look like string literals, as expected by some string constructors.
4534 const CHAR empty[0 + 1] = {CHAR('\0')};
4535 const CHAR hi[2 + 1] = {
4536 CHAR('h'),
4537 CHAR('i'),
4538 CHAR('\0'),
4541 // Literal empty string.
4542 MOZ_RELEASE_ASSERT(BSV(empty).Length() == 0);
4543 MOZ_RELEASE_ASSERT(BSV(empty).AsSpan().IsEmpty());
4544 MOZ_RELEASE_ASSERT(BSV(empty).IsLiteral());
4545 MOZ_RELEASE_ASSERT(!BSV(empty).IsReference());
4547 // Literal non-empty string.
4548 MOZ_RELEASE_ASSERT(BSV(hi).Length() == 2);
4549 MOZ_RELEASE_ASSERT(BSV(hi).AsSpan().Elements());
4550 MOZ_RELEASE_ASSERT(BSV(hi).AsSpan().Elements()[0] == CHAR('h'));
4551 MOZ_RELEASE_ASSERT(BSV(hi).AsSpan().Elements()[1] == CHAR('i'));
4552 MOZ_RELEASE_ASSERT(BSV(hi).IsLiteral());
4553 MOZ_RELEASE_ASSERT(!BSV(hi).IsReference());
4555 // std::string_view to a literal empty string.
4556 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>(empty)).Length() == 0);
4557 MOZ_RELEASE_ASSERT(
4558 BSV(std::basic_string_view<CHAR>(empty)).AsSpan().IsEmpty());
4559 MOZ_RELEASE_ASSERT(!BSV(std::basic_string_view<CHAR>(empty)).IsLiteral());
4560 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>(empty)).IsReference());
4562 // std::string_view to a literal non-empty string.
4563 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>(hi)).Length() == 2);
4564 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>(hi)).AsSpan().Elements());
4565 MOZ_RELEASE_ASSERT(
4566 BSV(std::basic_string_view<CHAR>(hi)).AsSpan().Elements()[0] ==
4567 CHAR('h'));
4568 MOZ_RELEASE_ASSERT(
4569 BSV(std::basic_string_view<CHAR>(hi)).AsSpan().Elements()[1] ==
4570 CHAR('i'));
4571 MOZ_RELEASE_ASSERT(!BSV(std::basic_string_view<CHAR>(hi)).IsLiteral());
4572 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>(hi)).IsReference());
4574 // Default std::string_view points at nullptr, ProfilerStringView converts it
4575 // to the literal empty string.
4576 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>()).Length() == 0);
4577 MOZ_RELEASE_ASSERT(!std::basic_string_view<CHAR>().data());
4578 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>()).AsSpan().IsEmpty());
4579 MOZ_RELEASE_ASSERT(BSV(std::basic_string_view<CHAR>()).IsLiteral());
4580 MOZ_RELEASE_ASSERT(!BSV(std::basic_string_view<CHAR>()).IsReference());
4582 // std::string to a literal empty string.
4583 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(empty)).Length() == 0);
4584 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(empty)).AsSpan().IsEmpty());
4585 MOZ_RELEASE_ASSERT(!BSV(std::basic_string<CHAR>(empty)).IsLiteral());
4586 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(empty)).IsReference());
4588 // std::string to a literal non-empty string.
4589 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(hi)).Length() == 2);
4590 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(hi)).AsSpan().Elements());
4591 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(hi)).AsSpan().Elements()[0] ==
4592 CHAR('h'));
4593 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(hi)).AsSpan().Elements()[1] ==
4594 CHAR('i'));
4595 MOZ_RELEASE_ASSERT(!BSV(std::basic_string<CHAR>(hi)).IsLiteral());
4596 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>(hi)).IsReference());
4598 // Default std::string contains an empty null-terminated string.
4599 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>()).Length() == 0);
4600 MOZ_RELEASE_ASSERT(std::basic_string<CHAR>().data());
4601 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>()).AsSpan().IsEmpty());
4602 MOZ_RELEASE_ASSERT(!BSV(std::basic_string<CHAR>()).IsLiteral());
4603 MOZ_RELEASE_ASSERT(BSV(std::basic_string<CHAR>()).IsReference());
4605 // Class that quacks like nsTString (with Data(), Length(), IsLiteral()), to
4606 // check that ProfilerStringView can read from them.
4607 class FakeNsTString {
4608 public:
4609 FakeNsTString(const CHAR* aData, size_t aLength, bool aIsLiteral)
4610 : mData(aData), mLength(aLength), mIsLiteral(aIsLiteral) {}
4612 const CHAR* Data() const { return mData; }
4613 size_t Length() const { return mLength; }
4614 bool IsLiteral() const { return mIsLiteral; }
4616 private:
4617 const CHAR* mData;
4618 size_t mLength;
4619 bool mIsLiteral;
4622 // FakeNsTString to nullptr.
4623 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(nullptr, 0, true)).Length() == 0);
4624 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(nullptr, 0, true)).AsSpan().IsEmpty());
4625 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(nullptr, 0, true)).IsLiteral());
4626 MOZ_RELEASE_ASSERT(!BSV(FakeNsTString(nullptr, 0, true)).IsReference());
4628 // FakeNsTString to a literal empty string.
4629 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(empty, 0, true)).Length() == 0);
4630 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(empty, 0, true)).AsSpan().IsEmpty());
4631 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(empty, 0, true)).IsLiteral());
4632 MOZ_RELEASE_ASSERT(!BSV(FakeNsTString(empty, 0, true)).IsReference());
4634 // FakeNsTString to a literal non-empty string.
4635 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, true)).Length() == 2);
4636 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, true)).AsSpan().Elements());
4637 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, true)).AsSpan().Elements()[0] ==
4638 CHAR('h'));
4639 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, true)).AsSpan().Elements()[1] ==
4640 CHAR('i'));
4641 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, true)).IsLiteral());
4642 MOZ_RELEASE_ASSERT(!BSV(FakeNsTString(hi, 2, true)).IsReference());
4644 // FakeNsTString to a non-literal non-empty string.
4645 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, false)).Length() == 2);
4646 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, false)).AsSpan().Elements());
4647 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, false)).AsSpan().Elements()[0] ==
4648 CHAR('h'));
4649 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, false)).AsSpan().Elements()[1] ==
4650 CHAR('i'));
4651 MOZ_RELEASE_ASSERT(!BSV(FakeNsTString(hi, 2, false)).IsLiteral());
4652 MOZ_RELEASE_ASSERT(BSV(FakeNsTString(hi, 2, false)).IsReference());
4654 // Serialization and deserialization (with ownership).
4655 constexpr size_t bufferMaxSize = 1024;
4656 constexpr ProfileChunkedBuffer::Length chunkMinSize = 128;
4657 ProfileBufferChunkManagerWithLocalLimit cm(bufferMaxSize, chunkMinSize);
4658 ProfileChunkedBuffer cb(ProfileChunkedBuffer::ThreadSafety::WithMutex, cm);
4660 // Literal string, serialized as raw pointer.
4661 MOZ_RELEASE_ASSERT(cb.PutObject(BSV(hi)));
4663 unsigned read = 0;
4664 ProfilerStringView<CHAR> outerBSV;
4665 cb.ReadEach([&](ProfileBufferEntryReader& aER) {
4666 ++read;
4667 auto bsv = aER.ReadObject<ProfilerStringView<CHAR>>();
4668 MOZ_RELEASE_ASSERT(bsv.Length() == 2);
4669 MOZ_RELEASE_ASSERT(bsv.AsSpan().Elements());
4670 MOZ_RELEASE_ASSERT(bsv.AsSpan().Elements()[0] == CHAR('h'));
4671 MOZ_RELEASE_ASSERT(bsv.AsSpan().Elements()[1] == CHAR('i'));
4672 MOZ_RELEASE_ASSERT(bsv.IsLiteral());
4673 MOZ_RELEASE_ASSERT(!bsv.IsReference());
4674 outerBSV = std::move(bsv);
4676 MOZ_RELEASE_ASSERT(read == 1);
4677 MOZ_RELEASE_ASSERT(outerBSV.Length() == 2);
4678 MOZ_RELEASE_ASSERT(outerBSV.AsSpan().Elements());
4679 MOZ_RELEASE_ASSERT(outerBSV.AsSpan().Elements()[0] == CHAR('h'));
4680 MOZ_RELEASE_ASSERT(outerBSV.AsSpan().Elements()[1] == CHAR('i'));
4681 MOZ_RELEASE_ASSERT(outerBSV.IsLiteral());
4682 MOZ_RELEASE_ASSERT(!outerBSV.IsReference());
4685 MOZ_RELEASE_ASSERT(cb.GetState().mRangeStart == 1u);
4687 cb.Clear();
4689 // Non-literal string, content is serialized.
4691 // We'll try to write 4 strings, such that the 4th one will cross into the
4692 // next chunk.
4693 unsigned guessedChunkBytes = unsigned(cb.GetState().mRangeStart) - 1u;
4694 static constexpr unsigned stringCount = 4u;
4695 const unsigned stringSize =
4696 guessedChunkBytes / stringCount / sizeof(CHAR) + 3u;
4698 std::basic_string<CHAR> longString;
4699 longString.reserve(stringSize);
4700 for (unsigned i = 0; i < stringSize; ++i) {
4701 longString += CHAR('0' + i);
4704 for (unsigned i = 0; i < stringCount; ++i) {
4705 MOZ_RELEASE_ASSERT(cb.PutObject(BSV(longString)));
4709 unsigned read = 0;
4710 ProfilerStringView<CHAR> outerBSV;
4711 cb.ReadEach([&](ProfileBufferEntryReader& aER) {
4712 ++read;
4714 auto bsv = aER.ReadObject<ProfilerStringView<CHAR>>();
4715 MOZ_RELEASE_ASSERT(bsv.Length() == stringSize);
4716 MOZ_RELEASE_ASSERT(bsv.AsSpan().Elements());
4717 for (unsigned i = 0; i < stringSize; ++i) {
4718 MOZ_RELEASE_ASSERT(bsv.AsSpan().Elements()[i] == CHAR('0' + i));
4719 longString += '0' + i;
4721 MOZ_RELEASE_ASSERT(!bsv.IsLiteral());
4722 // The first 3 should be references (because they fit in one chunk, so
4723 // they can be referenced directly), which the 4th one have to be copied
4724 // out of two chunks and stitched back together.
4725 MOZ_RELEASE_ASSERT(bsv.IsReference() == (read != 4));
4727 // Test move of ownership.
4728 outerBSV = std::move(bsv);
4729 // After a move, references stay complete, while a non-reference had a
4730 // buffer that has been moved out.
4731 // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
4732 MOZ_RELEASE_ASSERT(bsv.Length() == ((read != 4) ? stringSize : 0));
4735 MOZ_RELEASE_ASSERT(outerBSV.Length() == stringSize);
4736 MOZ_RELEASE_ASSERT(outerBSV.AsSpan().Elements());
4737 for (unsigned i = 0; i < stringSize; ++i) {
4738 MOZ_RELEASE_ASSERT(outerBSV.AsSpan().Elements()[i] == CHAR('0' + i));
4739 longString += '0' + i;
4741 MOZ_RELEASE_ASSERT(!outerBSV.IsLiteral());
4742 MOZ_RELEASE_ASSERT(outerBSV.IsReference() == (read != 4));
4744 MOZ_RELEASE_ASSERT(read == 4);
4747 if constexpr (std::is_same_v<CHAR, char>) {
4748 printf("TestProfilerStringView<char> done\n");
4749 } else if constexpr (std::is_same_v<CHAR, char16_t>) {
4750 printf("TestProfilerStringView<char16_t> done\n");
4754 void TestProfilerDependencies() {
4755 TestPowerOfTwoMask();
4756 TestPowerOfTwo();
4757 TestLEB128();
4758 TestJSONTimeOutput();
4759 TestChunk();
4760 TestChunkManagerSingle();
4761 TestChunkManagerWithLocalLimit();
4762 TestControlledChunkManagerUpdate();
4763 TestControlledChunkManagerWithLocalLimit();
4764 TestChunkedBuffer();
4765 TestChunkedBufferSingle();
4766 TestModuloBuffer();
4767 TestBlocksRingBufferAPI();
4768 TestBlocksRingBufferUnderlyingBufferChanges();
4769 TestBlocksRingBufferThreading();
4770 TestBlocksRingBufferSerialization();
4771 TestLiteralEmptyStringView();
4772 TestProfilerStringView<char>();
4773 TestProfilerStringView<char16_t>();
4776 // Increase the depth, to a maximum (to avoid too-deep recursion).
4777 static constexpr size_t NextDepth(size_t aDepth) {
4778 constexpr size_t MAX_DEPTH = 128;
4779 return (aDepth < MAX_DEPTH) ? (aDepth + 1) : aDepth;
4782 Atomic<bool, Relaxed> sStopFibonacci;
4784 // Compute fibonacci the hard way (recursively: `f(n)=f(n-1)+f(n-2)`), and
4785 // prevent inlining.
4786 // The template parameter makes each depth be a separate function, to better
4787 // distinguish them in the profiler output.
4788 template <size_t DEPTH = 0>
4789 MOZ_NEVER_INLINE unsigned long long Fibonacci(unsigned long long n) {
4790 AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING("fib", OTHER, std::to_string(DEPTH));
4791 if (n == 0) {
4792 return 0;
4794 if (n == 1) {
4795 return 1;
4797 if (DEPTH < 5 && sStopFibonacci) {
4798 return 1'000'000'000;
4800 TimeStamp start = TimeStamp::Now();
4801 static constexpr size_t MAX_MARKER_DEPTH = 10;
4802 unsigned long long f2 = Fibonacci<NextDepth(DEPTH)>(n - 2);
4803 if (DEPTH == 0) {
4804 BASE_PROFILER_MARKER_UNTYPED("Half-way through Fibonacci", OTHER);
4806 unsigned long long f1 = Fibonacci<NextDepth(DEPTH)>(n - 1);
4807 if (DEPTH < MAX_MARKER_DEPTH) {
4808 BASE_PROFILER_MARKER_TEXT("fib", OTHER,
4809 MarkerTiming::IntervalUntilNowFrom(start),
4810 std::to_string(DEPTH));
4812 return f2 + f1;
4815 void TestProfiler() {
4816 printf("TestProfiler starting -- pid: %" PRIu64 ", tid: %" PRIu64 "\n",
4817 uint64_t(baseprofiler::profiler_current_process_id().ToNumber()),
4818 uint64_t(baseprofiler::profiler_current_thread_id().ToNumber()));
4819 // ::SleepMilli(10000);
4821 TestProfilerDependencies();
4824 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_is_active());
4825 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_being_profiled());
4826 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_sleeping());
4828 const baseprofiler::BaseProfilerThreadId mainThreadId =
4829 mozilla::baseprofiler::profiler_current_thread_id();
4831 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::profiler_main_thread_id() ==
4832 mainThreadId);
4833 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::profiler_is_main_thread());
4835 std::thread testThread([&]() {
4836 const baseprofiler::BaseProfilerThreadId testThreadId =
4837 mozilla::baseprofiler::profiler_current_thread_id();
4838 MOZ_RELEASE_ASSERT(testThreadId != mainThreadId);
4840 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::profiler_main_thread_id() !=
4841 testThreadId);
4842 MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_is_main_thread());
4844 testThread.join();
4846 printf("profiler_start()...\n");
4847 Vector<const char*> filters;
4848 // Profile all registered threads.
4849 MOZ_RELEASE_ASSERT(filters.append(""));
4850 const uint32_t features = baseprofiler::ProfilerFeature::StackWalk;
4851 baseprofiler::profiler_start(baseprofiler::BASE_PROFILER_DEFAULT_ENTRIES,
4852 BASE_PROFILER_DEFAULT_INTERVAL, features,
4853 filters.begin(), filters.length());
4855 MOZ_RELEASE_ASSERT(baseprofiler::profiler_is_active());
4856 MOZ_RELEASE_ASSERT(baseprofiler::profiler_thread_is_being_profiled());
4857 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_sleeping());
4859 sStopFibonacci = false;
4861 std::thread threadFib([]() {
4862 AUTO_BASE_PROFILER_REGISTER_THREAD("fibonacci");
4863 SleepMilli(5);
4864 auto cause = baseprofiler::profiler_capture_backtrace();
4865 AUTO_BASE_PROFILER_MARKER_TEXT(
4866 "fibonacci", OTHER, MarkerStack::TakeBacktrace(std::move(cause)),
4867 "First leaf call");
4868 static const unsigned long long fibStart = 37;
4869 printf("Fibonacci(%llu)...\n", fibStart);
4870 AUTO_BASE_PROFILER_LABEL("Label around Fibonacci", OTHER);
4872 unsigned long long f = Fibonacci(fibStart);
4873 printf("Fibonacci(%llu) = %llu\n", fibStart, f);
4876 std::thread threadCancelFib([]() {
4877 AUTO_BASE_PROFILER_REGISTER_THREAD("fibonacci canceller");
4878 SleepMilli(5);
4879 AUTO_BASE_PROFILER_MARKER_TEXT("fibonacci", OTHER, {}, "Canceller");
4880 static const int waitMaxSeconds = 10;
4881 for (int i = 0; i < waitMaxSeconds; ++i) {
4882 if (sStopFibonacci) {
4883 AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING("fibCancel", OTHER,
4884 std::to_string(i));
4885 return;
4887 AUTO_BASE_PROFILER_THREAD_SLEEP;
4888 SleepMilli(1000);
4890 AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING("fibCancel", OTHER,
4891 "Cancelling!");
4892 sStopFibonacci = true;
4896 AUTO_BASE_PROFILER_MARKER_TEXT("main thread", OTHER, {},
4897 "joining fibonacci thread");
4898 AUTO_BASE_PROFILER_THREAD_SLEEP;
4899 threadFib.join();
4903 AUTO_BASE_PROFILER_MARKER_TEXT("main thread", OTHER, {},
4904 "joining fibonacci-canceller thread");
4905 sStopFibonacci = true;
4906 AUTO_BASE_PROFILER_THREAD_SLEEP;
4907 threadCancelFib.join();
4910 // Just making sure all payloads know how to (de)serialize and stream.
4912 MOZ_RELEASE_ASSERT(
4913 baseprofiler::AddMarker("markers 2.0 without options (omitted)",
4914 mozilla::baseprofiler::category::OTHER));
4916 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4917 "markers 2.0 without options (implicit brace-init)",
4918 mozilla::baseprofiler::category::OTHER, {}));
4920 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4921 "markers 2.0 without options (explicit init)",
4922 mozilla::baseprofiler::category::OTHER, MarkerOptions()));
4924 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4925 "markers 2.0 without options (explicit brace-init)",
4926 mozilla::baseprofiler::category::OTHER, MarkerOptions{}));
4928 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4929 "markers 2.0 with one option (implicit)",
4930 mozilla::baseprofiler::category::OTHER, MarkerInnerWindowId(123)));
4932 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4933 "markers 2.0 with one option (implicit brace-init)",
4934 mozilla::baseprofiler::category::OTHER, {MarkerInnerWindowId(123)}));
4936 MOZ_RELEASE_ASSERT(
4937 baseprofiler::AddMarker("markers 2.0 with one option (explicit init)",
4938 mozilla::baseprofiler::category::OTHER,
4939 MarkerOptions(MarkerInnerWindowId(123))));
4941 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4942 "markers 2.0 with one option (explicit brace-init)",
4943 mozilla::baseprofiler::category::OTHER,
4944 MarkerOptions{MarkerInnerWindowId(123)}));
4946 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4947 "markers 2.0 with two options (implicit brace-init)",
4948 mozilla::baseprofiler::category::OTHER,
4949 {MarkerInnerWindowId(123), MarkerStack::Capture()}));
4951 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4952 "markers 2.0 with two options (explicit init)",
4953 mozilla::baseprofiler::category::OTHER,
4954 MarkerOptions(MarkerInnerWindowId(123), MarkerStack::Capture())));
4956 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4957 "markers 2.0 with two options (explicit brace-init)",
4958 mozilla::baseprofiler::category::OTHER,
4959 MarkerOptions{MarkerInnerWindowId(123), MarkerStack::Capture()}));
4961 MOZ_RELEASE_ASSERT(
4962 baseprofiler::AddMarker("default-templated markers 2.0 without options",
4963 mozilla::baseprofiler::category::OTHER));
4965 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4966 "default-templated markers 2.0 with option",
4967 mozilla::baseprofiler::category::OTHER, MarkerInnerWindowId(123)));
4969 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4970 "explicitly-default-templated markers 2.0 without options",
4971 mozilla::baseprofiler::category::OTHER, {},
4972 ::mozilla::baseprofiler::markers::NoPayload{}));
4974 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4975 "explicitly-default-templated markers 2.0 with option",
4976 mozilla::baseprofiler::category::OTHER, MarkerInnerWindowId(123),
4977 ::mozilla::baseprofiler::markers::NoPayload{}));
4979 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4980 "tracing", mozilla::baseprofiler::category::OTHER, {},
4981 mozilla::baseprofiler::markers::Tracing{}, "category"));
4983 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4984 "text", mozilla::baseprofiler::category::OTHER, {},
4985 mozilla::baseprofiler::markers::TextMarker{}, "text text"));
4987 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4988 "media sample", mozilla::baseprofiler::category::OTHER, {},
4989 mozilla::baseprofiler::markers::MediaSampleMarker{}, 123, 456, 789));
4991 MOZ_RELEASE_ASSERT(baseprofiler::AddMarker(
4992 "video falling behind", mozilla::baseprofiler::category::OTHER, {},
4993 mozilla::baseprofiler::markers::VideoFallingBehindMarker{}, 123, 456));
4995 printf("Sleep 1s...\n");
4997 AUTO_BASE_PROFILER_THREAD_SLEEP;
4998 SleepMilli(1000);
5001 printf("baseprofiler_pause()...\n");
5002 baseprofiler::profiler_pause();
5004 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_being_profiled());
5006 Maybe<baseprofiler::ProfilerBufferInfo> info =
5007 baseprofiler::profiler_get_buffer_info();
5008 MOZ_RELEASE_ASSERT(info.isSome());
5009 printf("Profiler buffer range: %llu .. %llu (%llu bytes)\n",
5010 static_cast<unsigned long long>(info->mRangeStart),
5011 static_cast<unsigned long long>(info->mRangeEnd),
5012 // sizeof(ProfileBufferEntry) == 9
5013 (static_cast<unsigned long long>(info->mRangeEnd) -
5014 static_cast<unsigned long long>(info->mRangeStart)) *
5016 printf("Stats: min(us) .. mean(us) .. max(us) [count]\n");
5017 printf("- Intervals: %7.1f .. %7.1f .. %7.1f [%u]\n",
5018 info->mIntervalsUs.min,
5019 info->mIntervalsUs.sum / info->mIntervalsUs.n,
5020 info->mIntervalsUs.max, info->mIntervalsUs.n);
5021 printf("- Overheads: %7.1f .. %7.1f .. %7.1f [%u]\n",
5022 info->mOverheadsUs.min,
5023 info->mOverheadsUs.sum / info->mOverheadsUs.n,
5024 info->mOverheadsUs.max, info->mOverheadsUs.n);
5025 printf(" - Locking: %7.1f .. %7.1f .. %7.1f [%u]\n",
5026 info->mLockingsUs.min, info->mLockingsUs.sum / info->mLockingsUs.n,
5027 info->mLockingsUs.max, info->mLockingsUs.n);
5028 printf(" - Clearning: %7.1f .. %7.1f .. %7.1f [%u]\n",
5029 info->mCleaningsUs.min,
5030 info->mCleaningsUs.sum / info->mCleaningsUs.n,
5031 info->mCleaningsUs.max, info->mCleaningsUs.n);
5032 printf(" - Counters: %7.1f .. %7.1f .. %7.1f [%u]\n",
5033 info->mCountersUs.min, info->mCountersUs.sum / info->mCountersUs.n,
5034 info->mCountersUs.max, info->mCountersUs.n);
5035 printf(" - Threads: %7.1f .. %7.1f .. %7.1f [%u]\n",
5036 info->mThreadsUs.min, info->mThreadsUs.sum / info->mThreadsUs.n,
5037 info->mThreadsUs.max, info->mThreadsUs.n);
5039 printf("baseprofiler_get_profile()...\n");
5040 UniquePtr<char[]> profile = baseprofiler::profiler_get_profile();
5042 // Use a string view over the profile contents, for easier testing.
5043 std::string_view profileSV = profile.get();
5045 constexpr const auto svnpos = std::string_view::npos;
5046 // TODO: Properly parse profile and check fields.
5047 // Check for some expected marker schema JSON output.
5048 MOZ_RELEASE_ASSERT(profileSV.find("\"markerSchema\":[") != svnpos);
5049 MOZ_RELEASE_ASSERT(profileSV.find("\"name\":\"Text\",") != svnpos);
5050 MOZ_RELEASE_ASSERT(profileSV.find("\"name\":\"tracing\",") != svnpos);
5051 MOZ_RELEASE_ASSERT(profileSV.find("\"name\":\"MediaSample\",") != svnpos);
5052 MOZ_RELEASE_ASSERT(profileSV.find("\"display\":[") != svnpos);
5053 MOZ_RELEASE_ASSERT(profileSV.find("\"marker-chart\"") != svnpos);
5054 MOZ_RELEASE_ASSERT(profileSV.find("\"marker-table\"") != svnpos);
5055 MOZ_RELEASE_ASSERT(profileSV.find("\"format\":\"string\"") != svnpos);
5056 // TODO: Add more checks for what's expected in the profile. Some of them
5057 // are done in gtest's.
5059 printf("baseprofiler_save_profile_to_file()...\n");
5060 baseprofiler::baseprofiler_save_profile_to_file(
5061 "TestProfiler_profile.json");
5063 printf("profiler_stop()...\n");
5064 baseprofiler::profiler_stop();
5066 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_is_active());
5067 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_being_profiled());
5068 MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_sleeping());
5070 printf("profiler_shutdown()...\n");
5073 printf("TestProfiler done\n");
5076 // Minimal string escaping, similar to how C++ stringliterals should be entered,
5077 // to help update comparison strings in tests below.
5078 void printEscaped(std::string_view aString) {
5079 for (const char c : aString) {
5080 switch (c) {
5081 case '\n':
5082 fprintf(stderr, "\\n\n");
5083 break;
5084 case '"':
5085 fprintf(stderr, "\\\"");
5086 break;
5087 case '\\':
5088 fprintf(stderr, "\\\\");
5089 break;
5090 default:
5091 if (c >= ' ' && c <= '~') {
5092 fprintf(stderr, "%c", c);
5093 } else {
5094 fprintf(stderr, "\\x%02x", unsigned(c));
5096 break;
5101 // Run aF(SpliceableChunkedJSONWriter&, UniqueJSONStrings&) from inside a JSON
5102 // array, then output the string table, and compare the full output to
5103 // aExpected.
5104 template <typename F>
5105 static void VerifyUniqueStringContents(
5106 F&& aF, std::string_view aExpectedData,
5107 std::string_view aExpectedUniqueStrings,
5108 mozilla::baseprofiler::UniqueJSONStrings* aUniqueStringsOrNull = nullptr) {
5109 mozilla::baseprofiler::SpliceableChunkedJSONWriter writer{
5110 FailureLatchInfallibleSource::Singleton()};
5112 MOZ_RELEASE_ASSERT(!writer.ChunkedWriteFunc().Fallible());
5113 MOZ_RELEASE_ASSERT(!writer.ChunkedWriteFunc().Failed());
5114 MOZ_RELEASE_ASSERT(!writer.ChunkedWriteFunc().GetFailure());
5115 MOZ_RELEASE_ASSERT(&writer.ChunkedWriteFunc().SourceFailureLatch() ==
5116 &mozilla::FailureLatchInfallibleSource::Singleton());
5117 MOZ_RELEASE_ASSERT(
5118 &std::as_const(writer.ChunkedWriteFunc()).SourceFailureLatch() ==
5119 &mozilla::FailureLatchInfallibleSource::Singleton());
5121 MOZ_RELEASE_ASSERT(!writer.Fallible());
5122 MOZ_RELEASE_ASSERT(!writer.Failed());
5123 MOZ_RELEASE_ASSERT(!writer.GetFailure());
5124 MOZ_RELEASE_ASSERT(&writer.SourceFailureLatch() ==
5125 &mozilla::FailureLatchInfallibleSource::Singleton());
5126 MOZ_RELEASE_ASSERT(&std::as_const(writer).SourceFailureLatch() ==
5127 &mozilla::FailureLatchInfallibleSource::Singleton());
5129 // By default use a local UniqueJSONStrings, otherwise use the one provided.
5130 mozilla::baseprofiler::UniqueJSONStrings localUniqueStrings{
5131 FailureLatchInfallibleSource::Singleton()};
5132 MOZ_RELEASE_ASSERT(!localUniqueStrings.Fallible());
5133 MOZ_RELEASE_ASSERT(!localUniqueStrings.Failed());
5134 MOZ_RELEASE_ASSERT(!localUniqueStrings.GetFailure());
5135 MOZ_RELEASE_ASSERT(&localUniqueStrings.SourceFailureLatch() ==
5136 &mozilla::FailureLatchInfallibleSource::Singleton());
5137 MOZ_RELEASE_ASSERT(&std::as_const(localUniqueStrings).SourceFailureLatch() ==
5138 &mozilla::FailureLatchInfallibleSource::Singleton());
5140 mozilla::baseprofiler::UniqueJSONStrings& uniqueStrings =
5141 aUniqueStringsOrNull ? *aUniqueStringsOrNull : localUniqueStrings;
5142 MOZ_RELEASE_ASSERT(!uniqueStrings.Failed());
5143 MOZ_RELEASE_ASSERT(!uniqueStrings.GetFailure());
5145 writer.Start();
5147 writer.StartArrayProperty("data");
5148 { std::forward<F>(aF)(writer, uniqueStrings); }
5149 writer.EndArray();
5151 writer.StartArrayProperty("stringTable");
5152 { uniqueStrings.SpliceStringTableElements(writer); }
5153 writer.EndArray();
5155 writer.End();
5157 MOZ_RELEASE_ASSERT(!uniqueStrings.Failed());
5158 MOZ_RELEASE_ASSERT(!uniqueStrings.GetFailure());
5160 MOZ_RELEASE_ASSERT(!writer.ChunkedWriteFunc().Failed());
5161 MOZ_RELEASE_ASSERT(!writer.ChunkedWriteFunc().GetFailure());
5163 MOZ_RELEASE_ASSERT(!writer.Failed());
5164 MOZ_RELEASE_ASSERT(!writer.GetFailure());
5166 UniquePtr<char[]> jsonString = writer.ChunkedWriteFunc().CopyData();
5167 MOZ_RELEASE_ASSERT(jsonString);
5168 std::string_view jsonStringView(jsonString.get());
5169 const size_t length = writer.ChunkedWriteFunc().Length();
5170 MOZ_RELEASE_ASSERT(length == jsonStringView.length());
5171 std::string expected = "{\"data\":[";
5172 expected += aExpectedData;
5173 expected += "],\"stringTable\":[";
5174 expected += aExpectedUniqueStrings;
5175 expected += "]}";
5176 if (jsonStringView != expected) {
5177 fprintf(stderr,
5178 "Expected:\n"
5179 "------\n");
5180 printEscaped(expected);
5181 fprintf(stderr,
5182 "\n"
5183 "------\n"
5184 "Actual:\n"
5185 "------\n");
5186 printEscaped(jsonStringView);
5187 fprintf(stderr,
5188 "\n"
5189 "------\n");
5191 MOZ_RELEASE_ASSERT(jsonStringView == expected);
5194 void TestUniqueJSONStrings() {
5195 printf("TestUniqueJSONStrings...\n");
5197 using SCJW = mozilla::baseprofiler::SpliceableChunkedJSONWriter;
5198 using UJS = mozilla::baseprofiler::UniqueJSONStrings;
5200 // Empty everything.
5201 VerifyUniqueStringContents([](SCJW& aWriter, UJS& aUniqueStrings) {}, "", "");
5203 // Empty unique strings.
5204 VerifyUniqueStringContents(
5205 [](SCJW& aWriter, UJS& aUniqueStrings) {
5206 aWriter.StringElement("string");
5208 R"("string")", "");
5210 // One unique string.
5211 VerifyUniqueStringContents(
5212 [](SCJW& aWriter, UJS& aUniqueStrings) {
5213 aUniqueStrings.WriteElement(aWriter, "string");
5215 "0", R"("string")");
5217 // One unique string twice.
5218 VerifyUniqueStringContents(
5219 [](SCJW& aWriter, UJS& aUniqueStrings) {
5220 aUniqueStrings.WriteElement(aWriter, "string");
5221 aUniqueStrings.WriteElement(aWriter, "string");
5223 "0,0", R"("string")");
5225 // Two single unique strings.
5226 VerifyUniqueStringContents(
5227 [](SCJW& aWriter, UJS& aUniqueStrings) {
5228 aUniqueStrings.WriteElement(aWriter, "string0");
5229 aUniqueStrings.WriteElement(aWriter, "string1");
5231 "0,1", R"("string0","string1")");
5233 // Two unique strings with repetition.
5234 VerifyUniqueStringContents(
5235 [](SCJW& aWriter, UJS& aUniqueStrings) {
5236 aUniqueStrings.WriteElement(aWriter, "string0");
5237 aUniqueStrings.WriteElement(aWriter, "string1");
5238 aUniqueStrings.WriteElement(aWriter, "string0");
5240 "0,1,0", R"("string0","string1")");
5242 // Mix some object properties, for coverage.
5243 VerifyUniqueStringContents(
5244 [](SCJW& aWriter, UJS& aUniqueStrings) {
5245 aUniqueStrings.WriteElement(aWriter, "string0");
5246 aWriter.StartObjectElement();
5248 aUniqueStrings.WriteProperty(aWriter, "p0", "prop");
5249 aUniqueStrings.WriteProperty(aWriter, "p1", "string0");
5250 aUniqueStrings.WriteProperty(aWriter, "p2", "prop");
5252 aWriter.EndObject();
5253 aUniqueStrings.WriteElement(aWriter, "string1");
5254 aUniqueStrings.WriteElement(aWriter, "string0");
5255 aUniqueStrings.WriteElement(aWriter, "prop");
5257 R"(0,{"p0":1,"p1":0,"p2":1},2,0,1)", R"("string0","prop","string1")");
5259 // Unique string table with pre-existing data.
5261 UJS ujs{FailureLatchInfallibleSource::Singleton()};
5263 SCJW writer{FailureLatchInfallibleSource::Singleton()};
5264 ujs.WriteElement(writer, "external0");
5265 ujs.WriteElement(writer, "external1");
5266 ujs.WriteElement(writer, "external0");
5268 VerifyUniqueStringContents(
5269 [](SCJW& aWriter, UJS& aUniqueStrings) {
5270 aUniqueStrings.WriteElement(aWriter, "string0");
5271 aUniqueStrings.WriteElement(aWriter, "string1");
5272 aUniqueStrings.WriteElement(aWriter, "string0");
5274 "2,3,2", R"("external0","external1","string0","string1")", &ujs);
5277 // Unique string table with pre-existing data from another table.
5279 UJS ujs{FailureLatchInfallibleSource::Singleton()};
5281 SCJW writer{FailureLatchInfallibleSource::Singleton()};
5282 ujs.WriteElement(writer, "external0");
5283 ujs.WriteElement(writer, "external1");
5284 ujs.WriteElement(writer, "external0");
5286 UJS ujsCopy(FailureLatchInfallibleSource::Singleton(), ujs,
5287 mozilla::ProgressLogger{});
5288 VerifyUniqueStringContents(
5289 [](SCJW& aWriter, UJS& aUniqueStrings) {
5290 aUniqueStrings.WriteElement(aWriter, "string0");
5291 aUniqueStrings.WriteElement(aWriter, "string1");
5292 aUniqueStrings.WriteElement(aWriter, "string0");
5294 "2,3,2", R"("external0","external1","string0","string1")", &ujs);
5297 // Unique string table through SpliceableJSONWriter.
5298 VerifyUniqueStringContents(
5299 [](SCJW& aWriter, UJS& aUniqueStrings) {
5300 aWriter.SetUniqueStrings(aUniqueStrings);
5301 aWriter.UniqueStringElement("string0");
5302 aWriter.StartObjectElement();
5304 aWriter.UniqueStringProperty("p0", "prop");
5305 aWriter.UniqueStringProperty("p1", "string0");
5306 aWriter.UniqueStringProperty("p2", "prop");
5308 aWriter.EndObject();
5309 aWriter.UniqueStringElement("string1");
5310 aWriter.UniqueStringElement("string0");
5311 aWriter.UniqueStringElement("prop");
5312 aWriter.ResetUniqueStrings();
5314 R"(0,{"p0":1,"p1":0,"p2":1},2,0,1)", R"("string0","prop","string1")");
5316 printf("TestUniqueJSONStrings done\n");
5319 void StreamMarkers(const mozilla::ProfileChunkedBuffer& aBuffer,
5320 mozilla::baseprofiler::SpliceableJSONWriter& aWriter) {
5321 aWriter.StartArrayProperty("data");
5323 aBuffer.ReadEach([&](mozilla::ProfileBufferEntryReader& aEntryReader) {
5324 mozilla::ProfileBufferEntryKind entryKind =
5325 aEntryReader.ReadObject<mozilla::ProfileBufferEntryKind>();
5326 MOZ_RELEASE_ASSERT(entryKind == mozilla::ProfileBufferEntryKind::Marker);
5328 mozilla::base_profiler_markers_detail::DeserializeAfterKindAndStream(
5329 aEntryReader,
5330 [&](const mozilla::baseprofiler::BaseProfilerThreadId&) {
5331 return &aWriter;
5333 [&](mozilla::ProfileChunkedBuffer&) {
5334 aWriter.StringElement("Real backtrace would be here");
5336 [&](mozilla::base_profiler_markers_detail::Streaming::
5337 DeserializerTag) {});
5340 aWriter.EndArray();
5343 void PrintMarkers(const mozilla::ProfileChunkedBuffer& aBuffer) {
5344 mozilla::baseprofiler::SpliceableJSONWriter writer(
5345 mozilla::MakeUnique<mozilla::baseprofiler::OStreamJSONWriteFunc>(
5346 std::cout),
5347 FailureLatchInfallibleSource::Singleton());
5348 mozilla::baseprofiler::UniqueJSONStrings uniqueStrings{
5349 FailureLatchInfallibleSource::Singleton()};
5350 writer.SetUniqueStrings(uniqueStrings);
5351 writer.Start();
5353 StreamMarkers(aBuffer, writer);
5355 writer.StartArrayProperty("stringTable");
5356 { uniqueStrings.SpliceStringTableElements(writer); }
5357 writer.EndArray();
5359 writer.End();
5360 writer.ResetUniqueStrings();
5363 static void SubTestMarkerCategory(
5364 const mozilla::MarkerCategory& aMarkerCategory,
5365 const mozilla::baseprofiler::ProfilingCategoryPair& aProfilingCategoryPair,
5366 const mozilla::baseprofiler::ProfilingCategory& aProfilingCategory) {
5367 MOZ_RELEASE_ASSERT(aMarkerCategory.CategoryPair() == aProfilingCategoryPair,
5368 "Unexpected MarkerCategory::CategoryPair()");
5370 MOZ_RELEASE_ASSERT(
5371 mozilla::MarkerCategory(aProfilingCategoryPair).CategoryPair() ==
5372 aProfilingCategoryPair,
5373 "MarkerCategory(<name>).CategoryPair() should return <name>");
5375 MOZ_RELEASE_ASSERT(aMarkerCategory.GetCategory() == aProfilingCategory,
5376 "Unexpected MarkerCategory::GetCategory()");
5378 mozilla::ProfileBufferChunkManagerSingle chunkManager(512);
5379 mozilla::ProfileChunkedBuffer buffer(
5380 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager);
5381 mozilla::ProfileBufferBlockIndex i = buffer.PutObject(aMarkerCategory);
5382 MOZ_RELEASE_ASSERT(i != mozilla::ProfileBufferBlockIndex{},
5383 "Failed serialization");
5384 buffer.ReadEach([&](mozilla::ProfileBufferEntryReader& aER,
5385 mozilla::ProfileBufferBlockIndex aIndex) {
5386 MOZ_RELEASE_ASSERT(aIndex == i, "Unexpected deserialization index");
5387 const auto readCategory = aER.ReadObject<mozilla::MarkerCategory>();
5388 MOZ_RELEASE_ASSERT(aER.RemainingBytes() == 0,
5389 "Unexpected extra serialized bytes");
5390 MOZ_RELEASE_ASSERT(readCategory.CategoryPair() == aProfilingCategoryPair,
5391 "Incorrect deserialization value");
5395 void TestMarkerCategory() {
5396 printf("TestMarkerCategory...\n");
5398 mozilla::ProfileBufferChunkManagerSingle chunkManager(512);
5399 mozilla::ProfileChunkedBuffer buffer(
5400 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager);
5402 # define CATEGORY_ENUM_BEGIN_CATEGORY(name, labelAsString, color)
5403 # define CATEGORY_ENUM_SUBCATEGORY(supercategory, name, labelAsString) \
5404 static_assert( \
5405 std::is_same_v<decltype(mozilla::baseprofiler::category::name), \
5406 const mozilla::MarkerCategory>, \
5407 "baseprofiler::category::<name> should be a const MarkerCategory"); \
5409 SubTestMarkerCategory( \
5410 mozilla::baseprofiler::category::name, \
5411 mozilla::baseprofiler::ProfilingCategoryPair::name, \
5412 mozilla::baseprofiler::ProfilingCategory::supercategory);
5413 # define CATEGORY_ENUM_END_CATEGORY
5414 MOZ_PROFILING_CATEGORY_LIST(CATEGORY_ENUM_BEGIN_CATEGORY,
5415 CATEGORY_ENUM_SUBCATEGORY,
5416 CATEGORY_ENUM_END_CATEGORY)
5417 # undef CATEGORY_ENUM_BEGIN_CATEGORY
5418 # undef CATEGORY_ENUM_SUBCATEGORY
5419 # undef CATEGORY_ENUM_END_CATEGORY
5421 printf("TestMarkerCategory done\n");
5424 void TestMarkerThreadId() {
5425 printf("TestMarkerThreadId...\n");
5427 MOZ_RELEASE_ASSERT(MarkerThreadId{}.IsUnspecified());
5428 MOZ_RELEASE_ASSERT(!MarkerThreadId::MainThread().IsUnspecified());
5429 MOZ_RELEASE_ASSERT(!MarkerThreadId::CurrentThread().IsUnspecified());
5431 MOZ_RELEASE_ASSERT(!MarkerThreadId{
5432 mozilla::baseprofiler::BaseProfilerThreadId::FromNumber(42)}
5433 .IsUnspecified());
5434 MOZ_RELEASE_ASSERT(
5435 MarkerThreadId{
5436 mozilla::baseprofiler::BaseProfilerThreadId::FromNumber(42)}
5437 .ThreadId()
5438 .ToNumber() == 42);
5440 // We'll assume that this test runs in the main thread (which should be true
5441 // when called from the `main` function).
5442 MOZ_RELEASE_ASSERT(MarkerThreadId::MainThread().ThreadId() ==
5443 mozilla::baseprofiler::profiler_main_thread_id());
5445 MOZ_RELEASE_ASSERT(MarkerThreadId::CurrentThread().ThreadId() ==
5446 mozilla::baseprofiler::profiler_current_thread_id());
5448 MOZ_RELEASE_ASSERT(MarkerThreadId::CurrentThread().ThreadId() ==
5449 mozilla::baseprofiler::profiler_main_thread_id());
5451 std::thread testThread([]() {
5452 MOZ_RELEASE_ASSERT(!MarkerThreadId::MainThread().IsUnspecified());
5453 MOZ_RELEASE_ASSERT(!MarkerThreadId::CurrentThread().IsUnspecified());
5455 MOZ_RELEASE_ASSERT(MarkerThreadId::MainThread().ThreadId() ==
5456 mozilla::baseprofiler::profiler_main_thread_id());
5458 MOZ_RELEASE_ASSERT(MarkerThreadId::CurrentThread().ThreadId() ==
5459 mozilla::baseprofiler::profiler_current_thread_id());
5461 MOZ_RELEASE_ASSERT(MarkerThreadId::CurrentThread().ThreadId() !=
5462 mozilla::baseprofiler::profiler_main_thread_id());
5464 testThread.join();
5466 printf("TestMarkerThreadId done\n");
5469 void TestMarkerNoPayload() {
5470 printf("TestMarkerNoPayload...\n");
5472 mozilla::ProfileBufferChunkManagerSingle chunkManager(512);
5473 mozilla::ProfileChunkedBuffer buffer(
5474 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager);
5476 mozilla::ProfileBufferBlockIndex i0 =
5477 mozilla::baseprofiler::AddMarkerToBuffer(
5478 buffer, "literal", mozilla::baseprofiler::category::OTHER_Profiling);
5479 MOZ_RELEASE_ASSERT(i0);
5481 const std::string dynamic = "dynamic";
5482 mozilla::ProfileBufferBlockIndex i1 =
5483 mozilla::baseprofiler::AddMarkerToBuffer(
5484 buffer, dynamic,
5485 mozilla::baseprofiler::category::GRAPHICS_FlushingAsyncPaints, {});
5486 MOZ_RELEASE_ASSERT(i1);
5487 MOZ_RELEASE_ASSERT(i1 > i0);
5489 mozilla::ProfileBufferBlockIndex i2 =
5490 mozilla::baseprofiler::AddMarkerToBuffer(
5491 buffer, std::string_view("string_view"),
5492 mozilla::baseprofiler::category::GRAPHICS_FlushingAsyncPaints, {});
5493 MOZ_RELEASE_ASSERT(i2);
5494 MOZ_RELEASE_ASSERT(i2 > i1);
5496 # ifdef DEBUG
5497 buffer.Dump();
5498 # endif
5500 PrintMarkers(buffer);
5502 printf("TestMarkerNoPayload done\n");
5505 void TestUserMarker() {
5506 printf("TestUserMarker...\n");
5508 // User-defined marker type with text.
5509 // It's fine to define it right in the function where it's used.
5510 struct MarkerTypeTestMinimal {
5511 static constexpr Span<const char> MarkerTypeName() {
5512 return MakeStringSpan("test-minimal");
5514 static void StreamJSONMarkerData(
5515 mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
5516 const std::string& aText) {
5517 aWriter.StringProperty("text", aText);
5519 static mozilla::MarkerSchema MarkerTypeDisplay() {
5520 using MS = mozilla::MarkerSchema;
5521 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
5522 schema.SetTooltipLabel("tooltip for test-minimal");
5523 schema.AddKeyLabelFormatSearchable("text", "Text", MS::Format::String,
5524 MS::Searchable::Searchable);
5525 return schema;
5529 mozilla::ProfileBufferChunkManagerSingle chunkManager(1024);
5530 mozilla::ProfileChunkedBuffer buffer(
5531 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager);
5533 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5534 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling, {},
5535 MarkerTypeTestMinimal{}, std::string("payload text")));
5537 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5538 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling,
5539 mozilla::MarkerThreadId(
5540 mozilla::baseprofiler::BaseProfilerThreadId::FromNumber(123)),
5541 MarkerTypeTestMinimal{}, std::string("ThreadId(123)")));
5543 auto start = mozilla::TimeStamp::Now();
5545 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5546 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling,
5547 mozilla::MarkerTiming::InstantAt(start), MarkerTypeTestMinimal{},
5548 std::string("InstantAt(start)")));
5550 auto then = mozilla::TimeStamp::Now();
5552 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5553 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling,
5554 mozilla::MarkerTiming::IntervalStart(start), MarkerTypeTestMinimal{},
5555 std::string("IntervalStart(start)")));
5557 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5558 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling,
5559 mozilla::MarkerTiming::IntervalEnd(then), MarkerTypeTestMinimal{},
5560 std::string("IntervalEnd(then)")));
5562 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5563 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling,
5564 mozilla::MarkerTiming::Interval(start, then), MarkerTypeTestMinimal{},
5565 std::string("Interval(start, then)")));
5567 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5568 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling,
5569 mozilla::MarkerTiming::IntervalUntilNowFrom(start),
5570 MarkerTypeTestMinimal{}, std::string("IntervalUntilNowFrom(start)")));
5572 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5573 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling,
5574 mozilla::MarkerStack::NoStack(), MarkerTypeTestMinimal{},
5575 std::string("NoStack")));
5576 // Note: We cannot test stack-capture here, because the profiler is not
5577 // initialized.
5579 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5580 buffer, "test2", mozilla::baseprofiler::category::OTHER_Profiling,
5581 mozilla::MarkerInnerWindowId(123), MarkerTypeTestMinimal{},
5582 std::string("InnerWindowId(123)")));
5584 # ifdef DEBUG
5585 buffer.Dump();
5586 # endif
5588 PrintMarkers(buffer);
5590 printf("TestUserMarker done\n");
5593 void TestPredefinedMarkers() {
5594 printf("TestPredefinedMarkers...\n");
5596 mozilla::ProfileBufferChunkManagerSingle chunkManager(1024);
5597 mozilla::ProfileChunkedBuffer buffer(
5598 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager);
5600 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5601 buffer, std::string_view("tracing"),
5602 mozilla::baseprofiler::category::OTHER, {},
5603 mozilla::baseprofiler::markers::Tracing{}, "category"));
5605 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5606 buffer, std::string_view("text"), mozilla::baseprofiler::category::OTHER,
5607 {}, mozilla::baseprofiler::markers::TextMarker{}, "text text"));
5609 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5610 buffer, std::string_view("media"), mozilla::baseprofiler::category::OTHER,
5611 {}, mozilla::baseprofiler::markers::MediaSampleMarker{}, 123, 456, 789));
5613 MOZ_RELEASE_ASSERT(mozilla::baseprofiler::AddMarkerToBuffer(
5614 buffer, std::string_view("media"), mozilla::baseprofiler::category::OTHER,
5615 {}, mozilla::baseprofiler::markers::VideoFallingBehindMarker{}, 123,
5616 456));
5618 # ifdef DEBUG
5619 buffer.Dump();
5620 # endif
5622 PrintMarkers(buffer);
5624 printf("TestPredefinedMarkers done\n");
5627 void TestProfilerMarkers() {
5628 printf(
5629 "TestProfilerMarkers -- pid: %" PRIu64 ", tid: %" PRIu64 "\n",
5630 uint64_t(mozilla::baseprofiler::profiler_current_process_id().ToNumber()),
5631 uint64_t(mozilla::baseprofiler::profiler_current_thread_id().ToNumber()));
5632 // ::SleepMilli(10000);
5634 TestUniqueJSONStrings();
5635 TestMarkerCategory();
5636 TestMarkerThreadId();
5637 TestMarkerNoPayload();
5638 TestUserMarker();
5639 TestPredefinedMarkers();
5641 printf("TestProfilerMarkers done\n");
5644 #else // MOZ_GECKO_PROFILER
5646 // Testing that macros are still #defined (but do nothing) when
5647 // MOZ_GECKO_PROFILER is disabled.
5648 void TestProfiler() {
5649 // These don't need to make sense, we just want to know that they're defined
5650 // and don't do anything.
5652 # ifndef AUTO_BASE_PROFILER_INIT
5653 # error AUTO_BASE_PROFILER_INIT not #defined
5654 # endif // AUTO_BASE_PROFILER_INIT
5655 AUTO_BASE_PROFILER_INIT;
5657 # ifndef AUTO_BASE_PROFILER_MARKER_TEXT
5658 # error AUTO_BASE_PROFILER_MARKER_TEXT not #defined
5659 # endif // AUTO_BASE_PROFILER_MARKER_TEXT
5661 # ifndef AUTO_BASE_PROFILER_LABEL
5662 # error AUTO_BASE_PROFILER_LABEL not #defined
5663 # endif // AUTO_BASE_PROFILER_LABEL
5665 # ifndef AUTO_BASE_PROFILER_THREAD_SLEEP
5666 # error AUTO_BASE_PROFILER_THREAD_SLEEP not #defined
5667 # endif // AUTO_BASE_PROFILER_THREAD_SLEEP
5668 AUTO_BASE_PROFILER_THREAD_SLEEP;
5670 # ifndef BASE_PROFILER_MARKER_UNTYPED
5671 # error BASE_PROFILER_MARKER_UNTYPED not #defined
5672 # endif // BASE_PROFILER_MARKER_UNTYPED
5674 # ifndef BASE_PROFILER_MARKER
5675 # error BASE_PROFILER_MARKER not #defined
5676 # endif // BASE_PROFILER_MARKER
5678 # ifndef BASE_PROFILER_MARKER_TEXT
5679 # error BASE_PROFILER_MARKER_TEXT not #defined
5680 # endif // BASE_PROFILER_MARKER_TEXT
5682 MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_get_backtrace(),
5683 "profiler_get_backtrace should return nullptr");
5684 mozilla::ProfileChunkedBuffer buffer(
5685 mozilla::ProfileChunkedBuffer::ThreadSafety::WithoutMutex);
5686 MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_capture_backtrace_into(
5687 buffer, mozilla::StackCaptureOptions::Full),
5688 "profiler_capture_backtrace_into should return false");
5689 MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_capture_backtrace(),
5690 "profiler_capture_backtrace should return nullptr");
5693 // Testing that macros are still #defined (but do nothing) when
5694 // MOZ_GECKO_PROFILER is disabled.
5695 void TestProfilerMarkers() {
5696 // These don't need to make sense, we just want to know that they're defined
5697 // and don't do anything.
5700 #endif // MOZ_GECKO_PROFILER else
5702 #if defined(XP_WIN)
5703 int wmain()
5704 #else
5705 int main()
5706 #endif // defined(XP_WIN)
5708 #ifdef MOZ_GECKO_PROFILER
5709 printf("BaseTestProfiler -- pid: %" PRIu64 ", tid: %" PRIu64 "\n",
5710 uint64_t(baseprofiler::profiler_current_process_id().ToNumber()),
5711 uint64_t(baseprofiler::profiler_current_thread_id().ToNumber()));
5712 // ::SleepMilli(10000);
5713 #endif // MOZ_GECKO_PROFILER
5715 TestFailureLatch();
5716 TestProfilerUtils();
5717 TestBaseAndProfilerDetail();
5718 TestSharedMutex();
5719 TestProportionValue();
5720 TestProgressLogger();
5721 // Note that there are two `TestProfiler{,Markers}` functions above, depending
5722 // on whether MOZ_GECKO_PROFILER is #defined.
5724 printf("profiler_init()...\n");
5725 AUTO_BASE_PROFILER_INIT;
5727 TestProfiler();
5728 TestProfilerMarkers();
5731 return 0;