/* 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/. */

#include "ProfiledThreadData.h"

#include "ProfileBuffer.h"

#include "mozilla/BaseProfiler.h"
#include "mozilla/BaseProfileJSONWriter.h"

#if defined(GP_OS_darwin)
#  include <pthread.h>
#endif

namespace mozilla {
namespace baseprofiler {

ProfiledThreadData::ProfiledThreadData(ThreadInfo* aThreadInfo)
    : mThreadInfo(aThreadInfo) {}

ProfiledThreadData::~ProfiledThreadData() = default;

void ProfiledThreadData::StreamJSON(const ProfileBuffer& aBuffer,
                                    SpliceableJSONWriter& aWriter,
                                    const std::string& aProcessName,
                                    const std::string& aETLDplus1,
                                    const TimeStamp& aProcessStartTime,
                                    double aSinceTime) {
  UniqueStacks uniqueStacks;

  aWriter.SetUniqueStrings(uniqueStacks.UniqueStrings());

  aWriter.Start();
  {
    StreamSamplesAndMarkers(mThreadInfo->Name(), mThreadInfo->ThreadId(),
                            aBuffer, aWriter, aProcessName, aETLDplus1,
                            aProcessStartTime, mThreadInfo->RegisterTime(),
                            mUnregisterTime, aSinceTime, uniqueStacks);

    aWriter.StartObjectProperty("stackTable");
    {
      {
        JSONSchemaWriter schema(aWriter);
        schema.WriteField("prefix");
        schema.WriteField("frame");
      }

      aWriter.StartArrayProperty("data");
      {
        uniqueStacks.SpliceStackTableElements(aWriter);
      }
      aWriter.EndArray();
    }
    aWriter.EndObject();

    aWriter.StartObjectProperty("frameTable");
    {
      {
        JSONSchemaWriter schema(aWriter);
        schema.WriteField("location");
        schema.WriteField("relevantForJS");
        schema.WriteField("innerWindowID");
        schema.WriteField("implementation");
        schema.WriteField("line");
        schema.WriteField("column");
        schema.WriteField("category");
        schema.WriteField("subcategory");
      }

      aWriter.StartArrayProperty("data");
      {
        uniqueStacks.SpliceFrameTableElements(aWriter);
      }
      aWriter.EndArray();
    }
    aWriter.EndObject();

    aWriter.StartArrayProperty("stringTable");
    {
      std::move(uniqueStacks.UniqueStrings())
          .SpliceStringTableElements(aWriter);
    }
    aWriter.EndArray();
  }
  aWriter.End();

  aWriter.ResetUniqueStrings();
}

BaseProfilerThreadId StreamSamplesAndMarkers(
    const char* aName, BaseProfilerThreadId aThreadId,
    const ProfileBuffer& aBuffer, SpliceableJSONWriter& aWriter,
    const std::string& aProcessName, const std::string& aETLDplus1,
    const TimeStamp& aProcessStartTime, const TimeStamp& aRegisterTime,
    const TimeStamp& aUnregisterTime, double aSinceTime,
    UniqueStacks& aUniqueStacks) {
  BaseProfilerThreadId processedThreadId;

  aWriter.StringProperty(
      "processType",
      "(unknown)" /* XRE_GeckoProcessTypeToString(XRE_GetProcessType()) */);

  {
    std::string name = aName;
    // We currently need to distinguish threads output by Base Profiler from
    // those in Gecko Profiler, as the frontend could get confused and lose
    // tracks with the same name.
    // TODO: As part of the profilers de-duplication, thread data from both
    // profilers should end up in the same track, at which point this won't be
    // necessary anymore. See meta bug 1557566.
    name += " (pre-xul)";
    aWriter.StringProperty("name", name);
  }

  // Use given process name (if any).
  if (!aProcessName.empty()) {
    aWriter.StringProperty("processName", aProcessName);
  }
  if (!aETLDplus1.empty()) {
    aWriter.StringProperty("eTLD+1", aETLDplus1);
  }

  if (aRegisterTime) {
    aWriter.DoubleProperty(
        "registerTime", (aRegisterTime - aProcessStartTime).ToMilliseconds());
  } else {
    aWriter.NullProperty("registerTime");
  }

  if (aUnregisterTime) {
    aWriter.DoubleProperty(
        "unregisterTime",
        (aUnregisterTime - aProcessStartTime).ToMilliseconds());
  } else {
    aWriter.NullProperty("unregisterTime");
  }

  aWriter.StartObjectProperty("samples");
  {
    {
      JSONSchemaWriter schema(aWriter);
      schema.WriteField("stack");
      schema.WriteField("time");
      schema.WriteField("eventDelay");
    }

    aWriter.StartArrayProperty("data");
    {
      processedThreadId = aBuffer.StreamSamplesToJSON(
          aWriter, aThreadId, aSinceTime, aUniqueStacks);
    }
    aWriter.EndArray();
  }
  aWriter.EndObject();

  aWriter.StartObjectProperty("markers");
  {
    {
      JSONSchemaWriter schema(aWriter);
      schema.WriteField("name");
      schema.WriteField("startTime");
      schema.WriteField("endTime");
      schema.WriteField("phase");
      schema.WriteField("category");
      schema.WriteField("data");
    }

    aWriter.StartArrayProperty("data");
    {
      aBuffer.StreamMarkersToJSON(aWriter, aThreadId, aProcessStartTime,
                                  aSinceTime, aUniqueStacks);
    }
    aWriter.EndArray();
  }
  aWriter.EndObject();

  // Tech note: If `ToNumber()` returns a uint64_t, the conversion to int64_t is
  // "implementation-defined" before C++20. This is acceptable here, because
  // this is a one-way conversion to a unique identifier that's used to visually
  // separate data by thread on the front-end.
  aWriter.IntProperty(
      "pid", static_cast<int64_t>(profiler_current_process_id().ToNumber()));
  aWriter.IntProperty("tid",
                      static_cast<int64_t>(aThreadId.IsSpecified()
                                               ? aThreadId.ToNumber()
                                               : processedThreadId.ToNumber()));

  return processedThreadId;
}

}  // namespace baseprofiler
}  // namespace mozilla
