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