summaryrefslogtreecommitdiffstats
path: root/tools/profiler/tests/xpcshell/test_feature_nativeallocations.js
blob: 64398d7ef93f2501674e60d1ae1079ca3fd004a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

add_task(async () => {
  if (!Services.profiler.GetFeatures().includes("nativeallocations")) {
    Assert.ok(
      true,
      "Native allocations are not supported by this build, " +
        "skip run the rest of the test."
    );
    return;
  }

  Assert.ok(
    !Services.profiler.IsActive(),
    "The profiler is not currently active"
  );

  info(
    "Test that the profiler can install memory hooks and collect native allocation " +
      "information in the marker payloads."
  );
  {
    info("Start the profiler.");
    await startProfiler({
      // Only instrument the main thread.
      threads: ["GeckoMain"],
      features: ["js", "nativeallocations"],
    });

    info(
      "Do some JS work for a little bit. This will increase the amount of allocations " +
        "that take place."
    );
    doWork();

    info("Get the profile data and analyze it.");
    const profile = await waitSamplingAndStopAndGetProfile();

    const {
      allocationPayloads,
      unmatchedAllocations,
      logAllocationsAndDeallocations,
    } = getAllocationInformation(profile);

    Assert.greater(
      allocationPayloads.length,
      0,
      "Native allocation payloads were recorded for the parent process' main thread when " +
        "the Native Allocation feature was turned on."
    );

    if (unmatchedAllocations.length !== 0) {
      info(
        "There were unmatched allocations. Log all of the allocations and " +
          "deallocations in order to aid debugging."
      );
      logAllocationsAndDeallocations();
      ok(
        false,
        "Found a deallocation that did not have a matching allocation site. " +
          "This could happen if balanced allocations is broken, or if the the " +
          "buffer size of this test was too small, and some markers ended up " +
          "rolling off."
      );
    }

    ok(true, "All deallocation sites had matching allocations.");
  }

  info("Restart the profiler, to ensure that we get no more allocations.");
  {
    await startProfiler({ features: ["js"] });
    info("Do some work again.");
    doWork();
    info("Wait for the periodic sampling.");
    const profile = await waitSamplingAndStopAndGetProfile();
    const allocationPayloads = getPayloadsOfType(
      profile.threads[0],
      "Native allocation"
    );

    Assert.equal(
      allocationPayloads.length,
      0,
      "No native allocations were collected when the feature was disabled."
    );
  }
});

function doWork() {
  this.n = 0;
  for (let i = 0; i < 1e5; i++) {
    this.n += Math.random();
  }
}

/**
 * Extract the allocation payloads, and find the unmatched allocations.
 */
function getAllocationInformation(profile) {
  // Get all of the allocation payloads.
  const allocationPayloads = getPayloadsOfType(
    profile.threads[0],
    "Native allocation"
  );

  // Decide what is an allocation and deallocation.
  const allocations = allocationPayloads.filter(
    payload => ensureIsNumber(payload.size) >= 0
  );
  const deallocations = allocationPayloads.filter(
    payload => ensureIsNumber(payload.size) < 0
  );

  // Now determine the unmatched allocations by building a set
  const allocationSites = new Set(
    allocations.map(({ memoryAddress }) => memoryAddress)
  );

  const unmatchedAllocations = deallocations.filter(
    ({ memoryAddress }) => !allocationSites.has(memoryAddress)
  );

  // Provide a helper to log out the allocations and deallocations on failure.
  function logAllocationsAndDeallocations() {
    for (const { memoryAddress } of allocations) {
      console.log("Allocations", formatHex(memoryAddress));
      allocationSites.add(memoryAddress);
    }

    for (const { memoryAddress } of deallocations) {
      console.log("Deallocations", formatHex(memoryAddress));
    }

    for (const { memoryAddress } of unmatchedAllocations) {
      console.log("Deallocation with no allocation", formatHex(memoryAddress));
    }
  }

  return {
    allocationPayloads,
    unmatchedAllocations,
    logAllocationsAndDeallocations,
  };
}

function ensureIsNumber(value) {
  if (typeof value !== "number") {
    throw new Error(`Expected a number: ${value}`);
  }
  return value;
}

function formatHex(number) {
  return `0x${number.toString(16)}`;
}