/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/
*/

"use strict";

ChromeUtils.import("resource://gre/modules/GCTelemetry.jsm", this);

function do_register_cleanup() {
  GCTelemetry.shutdown();
}

/*
 * These tests are very basic, my goal was to add enough testing to support
 * a change made in Bug 1424760.  TODO Bug 1429635 for adding more extensive
 * tests and deleting this comment.
 */

function run_test() {
  // The limit on the number of fields in a GCMajor object.
  const limit = 24;

  // The number of fields that the make_gc() test function generates.
  const make_gc_fields = 19;

  // Test initialisation
  Assert.ok(GCTelemetry.init(), "Initialize success");
  Assert.ok(!GCTelemetry.init(), "Wont initialize twice");

  // Test the basic success path.

  // There are currently no entries
  assert_num_entries(0, false);

  // Add an entry
  GCTelemetry.observeRaw(make_gc());
  // Get it back.
  assert_num_entries(1, false);
  Assert.equal(make_gc_fields, Object.keys(get_entry()).length);
  // "true" will cause the entry to be clared.
  assert_num_entries(1, true);
  // There are currently no entries.
  assert_num_entries(0, false);

  // Test too many fields
  let my_big_gc = make_gc();
  for (let i = 0; i < 100; i++) {
    my_big_gc["new_property_" + i] = "Data";
  }
  GCTelemetry.observeRaw(my_big_gc);
  // Assert that it was recorded but has only 7 fields.
  Assert.equal(7, Object.keys(get_entry()).length);
  assert_num_entries(1, true);
  assert_num_entries(0, false);

  let my_gc_exact = make_gc();
  Assert.equal(make_gc_fields, Object.keys(my_gc_exact).length);

  for (let i = 0; i < limit - make_gc_fields; i++) {
    my_gc_exact["new_property_" + i] = "Data";
  }
  GCTelemetry.observeRaw(my_gc_exact);
  // Assert that it was recorded has all the fields.
  Assert.equal(limit, Object.keys(get_entry()).length);
  assert_num_entries(1, true);
  assert_num_entries(0, false);

  // Exactly too many fields.
  let my_gc_too_many = make_gc();
  for (let i = 0; i < limit - make_gc_fields + 1; i++) {
    my_gc_too_many["new_property_" + i] = "Data";
  }
  GCTelemetry.observeRaw(my_gc_too_many);
  // Assert that it was recorded but has only 7 fields.
  Assert.equal(7, Object.keys(get_entry()).length);
  assert_num_entries(1, true);
  assert_num_entries(0, false);
}

function assert_num_entries(expect, clear) {
  let entries = GCTelemetry.entries("main", clear);
  Assert.equal(expect, entries.worst.length, expect + " worst entries");
  // Randomly sampled GCs are only recorded for content processes
  Assert.equal(0, entries.random.length, expect + " random entries");
}

function get_entry() {
  let entries = GCTelemetry.entries("main", false);
  Assert.ok(entries, "Got entries object");
  Assert.ok(entries.random, "Has random property");
  Assert.ok(entries.worst, "Has worst property");
  let entry = entries.worst[0];
  Assert.ok(entry, "Got worst entry");
  return entry;
}

/*
 * These events are not exactly well-formed, but good enough.  For example
 * there's no guantee that all the pause times add up to total time, or
 * that max_pause is correct.
 */
function make_gc() {
  // Timestamps are in milliseconds since startup. All the times here
  // are wall-clock times, which may not be monotonically increasing.
  let timestamp = Math.random() * 1000000;

  let gc = {
    status: "completed",
    timestamp,
    // All durations are in milliseconds.
    max_pause: Math.random() * 95 + 5,
    total_time: Math.random() * 500 + 500, // Sum of all slice times.
    zones_collected: 9,
    total_zones: 9,
    total_compartments: 309,
    minor_gcs: 44,
    store_buffer_overflows: 19,
    mmu_20ms: 0,
    mmu_50ms: 0,
    nonincremental_reason: "GCBytesTrigger",
    allocated_bytes: 38853696, // in bytes
    added_chunks: 54,
    removed_chunks: 12,
    slices: 15,
    slice_number: 218, // The first slice number for this GC event.
    slices_list: [
      {
        slice: 218, // The global index of this slice.
        pause: Math.random() * 2 + 28,
        reason: "SET_NEW_DOCUMENT",
        initial_state: "NotActive",
        final_state: "Mark",
        budget: "10ms",
        page_faults: 1,
        start_timestamp: timestamp + Math.random() * 50000,
        times: {
          wait_background_thread: 0.012,
          mark_discard_code: 2.845,
          purge: 0.723,
          mark: 9.831,
          mark_roots: 0.102,
          buffer_gray_roots: 3.095,
          mark_cross_compartment_wrappers: 0.039,
          mark_c_and_js_stacks: 0.005,
          mark_runtime_wide_data: 2.313,
          mark_embedding: 0.117,
          mark_compartments: 2.27,
          unmark: 1.063,
          minor_gcs_to_evict_nursery: 8.701,
        },
      },
    ],
    totals: {
      wait_background_thread: 0.012,
      mark_discard_code: 2.845,
      purge: 0.723,
      mark: 9.831,
      mark_roots: 0.102,
      buffer_gray_roots: 3.095,
      mark_cross_compartment_wrappers: 0.039,
      mark_c_and_js_stacks: 0.005,
      mark_runtime_wide_data: 2.313,
      mark_embedding: 0.117,
      mark_compartments: 2.27,
      unmark: 1.063,
      minor_gcs_to_evict_nursery: 8.701,
    },
  };
  return gc;
}
