summaryrefslogtreecommitdiff
path: root/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js
blob: e8c9f868ab3f78729915723164a21da024aee819 (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
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

Cu.import("resource://gre/modules/Services.jsm");

function getMainThreadHangStats() {
  let threads = Services.telemetry.threadHangStats;
  return threads.find((thread) => (thread.name === "Gecko"));
}

function run_test() {
  let startHangs = getMainThreadHangStats();

  // We disable hang reporting in several situations (e.g. debug builds,
  // official releases). In those cases, we don't have hang stats available
  // and should exit the test early.
  if (!startHangs) {
    ok("Hang reporting not enabled.");
    return;
  }

  if (Services.appinfo.OS === 'Linux' || Services.appinfo.OS === 'Android') {
    // We use the rt_tgsigqueueinfo syscall on Linux which requires a
    // certain kernel version. It's not an error if the system running
    // the test is older than that.
    let kernel = Services.sysinfo.get('kernel_version') ||
                 Services.sysinfo.get('version');
    if (Services.vc.compare(kernel, '2.6.31') < 0) {
      ok("Hang reporting not supported for old kernel.");
      return;
    }
  }

  // Run three events in the event loop:
  // the first event causes a transient hang;
  // the second event causes a permanent hang;
  // the third event checks results from previous events.

  do_execute_soon(() => {
    // Cause a hang lasting 1 second (transient hang).
    let startTime = Date.now();
    while ((Date.now() - startTime) < 1000);
  });

  do_execute_soon(() => {
    // Cause a hang lasting 10 seconds (permanent hang).
    let startTime = Date.now();
    while ((Date.now() - startTime) < 10000);
  });

  do_execute_soon(() => {
    do_test_pending();

    let check_results = () => {
      let endHangs = getMainThreadHangStats();

      // Because hangs are recorded asynchronously, if we don't see new hangs,
      // we should wait for pending hangs to be recorded. On the other hand,
      // if hang monitoring is broken, this test will time out.
      if (endHangs.hangs.length === startHangs.hangs.length) {
        do_timeout(100, check_results);
        return;
      }

      let check_histogram = (histogram) => {
        equal(typeof histogram, "object");
        equal(histogram.histogram_type, 0);
        equal(typeof histogram.min, "number");
        equal(typeof histogram.max, "number");
        equal(typeof histogram.sum, "number");
        ok(Array.isArray(histogram.ranges));
        ok(Array.isArray(histogram.counts));
        equal(histogram.counts.length, histogram.ranges.length);
      };

      // Make sure the hang stats structure is what we expect.
      equal(typeof endHangs, "object");
      check_histogram(endHangs.activity);

      ok(Array.isArray(endHangs.hangs));
      notEqual(endHangs.hangs.length, 0);

      ok(Array.isArray(endHangs.hangs[0].stack));
      notEqual(endHangs.hangs[0].stack.length, 0);
      equal(typeof endHangs.hangs[0].stack[0], "string");

      // Make sure one of the hangs is a permanent
      // hang containing a native stack.
      ok(endHangs.hangs.some((hang) => (
        Array.isArray(hang.nativeStack) &&
        hang.nativeStack.length !== 0 &&
        typeof hang.nativeStack[0] === "string"
      )));

      check_histogram(endHangs.hangs[0].histogram);

      do_test_finished();
    };

    check_results();
  });
}