/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/traced/service/builtin_producer.h"

#include <sys/types.h>

#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/proc_utils.h"
#include "perfetto/ext/base/metatrace.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/base/weak_ptr.h"
#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/client_identity.h"
#include "perfetto/ext/tracing/core/trace_writer.h"
#include "perfetto/ext/tracing/core/tracing_service.h"
#include "perfetto/tracing/core/data_source_config.h"
#include "perfetto/tracing/core/data_source_descriptor.h"
#include "src/tracing/service/metatrace_writer.h"

#include "protos/perfetto/config/android/android_sdk_sysprop_guard_config.pbzero.h"

// This translation unit is only ever used in Android in-tree builds.
// These producers are here  to dynamically start heapprofd and other services
// via sysprops when a trace that requests them is active. That can only happen
// in in-tree builds of Android.

#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
#include <sys/system_properties.h>
#endif

namespace perfetto {

namespace {

constexpr char kHeapprofdDataSourceName[] = "android.heapprofd";
constexpr char kJavaHprofDataSourceName[] = "android.java_hprof";
constexpr char kJavaHprofOomDataSourceName[] = "android.java_hprof.oom";
constexpr char kTracedPerfDataSourceName[] = "linux.perf";
constexpr char kLazyHeapprofdPropertyName[] = "traced.lazy.heapprofd";
constexpr char kLazyTracedPerfPropertyName[] = "traced.lazy.traced_perf";
constexpr char kJavaHprofOomActivePropertyName[] =
    "traced.oome_heap_session.count";

constexpr char kAndroidSdkSyspropGuardDataSourceName[] =
    "android.sdk_sysprop_guard";
constexpr char kPerfettoSdkSyspropGuardGenerationPropertyName[] =
    "debug.tracing.ctl.perfetto.sdk_sysprop_guard_generation";
constexpr char kHwuiSkiaBroadTracingPropertyName[] =
    "debug.tracing.ctl.hwui.skia_tracing_enabled";
constexpr char kHwuiSkiaUsePerfettoPropertyName[] =
    "debug.tracing.ctl.hwui.skia_use_perfetto_track_events";
constexpr char kHwuiSkiaPropertyPackageSeparator[] = ".";
constexpr char kSurfaceFlingerSkiaBroadTracingPropertyName[] =
    "debug.tracing.ctl.renderengine.skia_tracing_enabled";
constexpr char kSurfaceFlingerSkiaUsePerfettoPropertyName[] =
    "debug.tracing.ctl.renderengine.skia_use_perfetto_track_events";

}  // namespace

BuiltinProducer::BuiltinProducer(base::TaskRunner* task_runner,
                                 uint32_t lazy_stop_delay_ms)
    : task_runner_(task_runner), weak_factory_(this) {
  lazy_heapprofd_.stop_delay_ms = lazy_stop_delay_ms;
  lazy_traced_perf_.stop_delay_ms = lazy_stop_delay_ms;
}

BuiltinProducer::~BuiltinProducer() {
  if (!lazy_heapprofd_.instance_ids.empty())
    SetAndroidProperty(kLazyHeapprofdPropertyName, "");
  if (!lazy_traced_perf_.instance_ids.empty())
    SetAndroidProperty(kLazyTracedPerfPropertyName, "");
  if (!java_hprof_oome_instances_.empty())
    SetAndroidProperty(kJavaHprofOomActivePropertyName, "");
}

void BuiltinProducer::ConnectInProcess(TracingService* svc) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
  // TODO(primiano): ConnectProducer should take a base::PlatformProcessId not
  // pid_t, as they are different on Windows. But that is a larger refactoring
  // and not worth given this is the only use case where it clashes.
  const pid_t cur_proc_id = 0;
#else
  const pid_t cur_proc_id = base::GetProcessId();
#endif
  endpoint_ = svc->ConnectProducer(
      this, ClientIdentity(base::GetCurrentUserId(), cur_proc_id), "traced",
      /*shared_memory_size_hint_bytes=*/16 * 1024, /*in_process=*/true,
      TracingService::ProducerSMBScrapingMode::kDisabled,
      /*shared_memory_page_size_hint_bytes=*/4096);
}

void BuiltinProducer::OnConnect() {
  DataSourceDescriptor metatrace_dsd;
  metatrace_dsd.set_name(MetatraceWriter::kDataSourceName);
  metatrace_dsd.set_will_notify_on_stop(true);
  endpoint_->RegisterDataSource(metatrace_dsd);
  {
    DataSourceDescriptor lazy_heapprofd_dsd;
    lazy_heapprofd_dsd.set_name(kHeapprofdDataSourceName);
    endpoint_->RegisterDataSource(lazy_heapprofd_dsd);
  }
  {
    DataSourceDescriptor lazy_java_hprof_dsd;
    lazy_java_hprof_dsd.set_name(kJavaHprofDataSourceName);
    endpoint_->RegisterDataSource(lazy_java_hprof_dsd);
  }
  {
    DataSourceDescriptor lazy_traced_perf_dsd;
    lazy_traced_perf_dsd.set_name(kTracedPerfDataSourceName);
    endpoint_->RegisterDataSource(lazy_traced_perf_dsd);
  }
  {
    DataSourceDescriptor java_hprof_oome_dsd;
    java_hprof_oome_dsd.set_name(kJavaHprofOomDataSourceName);
    endpoint_->RegisterDataSource(java_hprof_oome_dsd);
  }
  {
    DataSourceDescriptor track_event_dsd;
    track_event_dsd.set_name(kAndroidSdkSyspropGuardDataSourceName);
    endpoint_->RegisterDataSource(track_event_dsd);
  }
}

void BuiltinProducer::SetupDataSource(DataSourceInstanceID ds_id,
                                      const DataSourceConfig& ds_config) {
  if (ds_config.name() == kHeapprofdDataSourceName ||
      ds_config.name() == kJavaHprofDataSourceName) {
    SetAndroidProperty(kLazyHeapprofdPropertyName, "1");
    lazy_heapprofd_.generation++;
    lazy_heapprofd_.instance_ids.emplace(ds_id);
    return;
  }

  if (ds_config.name() == kTracedPerfDataSourceName) {
    SetAndroidProperty(kLazyTracedPerfPropertyName, "1");
    lazy_traced_perf_.generation++;
    lazy_traced_perf_.instance_ids.emplace(ds_id);
    return;
  }

  if (ds_config.name() == kJavaHprofOomDataSourceName) {
    java_hprof_oome_instances_.emplace(ds_id);
    SetAndroidProperty(kJavaHprofOomActivePropertyName,
                       std::to_string(java_hprof_oome_instances_.size()));
    return;
  }

  // TODO(b/281329340): delete this when no longer needed.
  if (ds_config.name() == kAndroidSdkSyspropGuardDataSourceName) {
    protos::pbzero::AndroidSdkSyspropGuardConfig::Decoder sysprop_guard_config(
        ds_config.android_sdk_sysprop_guard_config_raw());
    std::vector<std::string> hwui_package_name_filter;
    for (auto package = sysprop_guard_config.hwui_package_name_filter();
         package; ++package) {
      hwui_package_name_filter.emplace_back((*package).ToStdString());
    }

    bool increase_generation = false;

    // SurfaceFlinger / RenderEngine
    if (sysprop_guard_config.surfaceflinger_skia_track_events() &&
        !android_sdk_sysprop_guard_state_.surfaceflinger_initialized) {
      SetAndroidProperty(kSurfaceFlingerSkiaBroadTracingPropertyName, "true");
      SetAndroidProperty(kSurfaceFlingerSkiaUsePerfettoPropertyName, "true");
      android_sdk_sysprop_guard_state_.surfaceflinger_initialized = true;
      increase_generation = true;
    }

    // HWUI apps
    if (sysprop_guard_config.hwui_skia_track_events()) {
      if (hwui_package_name_filter.size() > 0) {
        // Set per-app flags
        for (std::string package : hwui_package_name_filter) {
          if (android_sdk_sysprop_guard_state_.hwui_packages_initialized.count(
                  package) == 0) {
            SetAndroidProperty(
                kHwuiSkiaBroadTracingPropertyName +
                    (kHwuiSkiaPropertyPackageSeparator + package),
                "true");
            SetAndroidProperty(
                kHwuiSkiaUsePerfettoPropertyName +
                    (kHwuiSkiaPropertyPackageSeparator + package),
                "true");
            android_sdk_sysprop_guard_state_.hwui_packages_initialized.insert(
                package);
            increase_generation = true;
          }
        }
      } else if (!android_sdk_sysprop_guard_state_.hwui_globally_initialized) {
        // Set global flag
        SetAndroidProperty(kHwuiSkiaBroadTracingPropertyName, "true");
        SetAndroidProperty(kHwuiSkiaUsePerfettoPropertyName, "true");
        android_sdk_sysprop_guard_state_.hwui_globally_initialized = true;
        increase_generation = true;
      }
    }

    if (increase_generation) {
      android_sdk_sysprop_guard_state_.generation++;
      SetAndroidProperty(
          kPerfettoSdkSyspropGuardGenerationPropertyName,
          std::to_string(android_sdk_sysprop_guard_state_.generation));
    }

    return;
  }
}

void BuiltinProducer::StartDataSource(DataSourceInstanceID ds_id,
                                      const DataSourceConfig& ds_config) {
  // We slightly rely on the fact that since this producer is in-process for
  // enabling metatrace early (relative to producers that are notified via IPC).
  if (ds_config.name() == MetatraceWriter::kDataSourceName) {
    auto writer = endpoint_->CreateTraceWriter(
        static_cast<BufferID>(ds_config.target_buffer()));

    auto it_and_inserted = metatrace_.writers.emplace(
        std::piecewise_construct, std::make_tuple(ds_id), std::make_tuple());
    PERFETTO_DCHECK(it_and_inserted.second);
    // Note: only the first concurrent writer will actually be active.
    metatrace_.writers[ds_id].Enable(task_runner_, std::move(writer),
                                     metatrace::TAG_ANY);
  }
}

void BuiltinProducer::StopDataSource(DataSourceInstanceID ds_id) {
  auto meta_it = metatrace_.writers.find(ds_id);
  if (meta_it != metatrace_.writers.end()) {
    // Synchronously re-flush the metatrace writer to record more of the
    // teardown interactions, then ack the stop.
    meta_it->second.WriteAllAndFlushTraceWriter([] {});
    metatrace_.writers.erase(meta_it);
    endpoint_->NotifyDataSourceStopped(ds_id);
    return;
  }

  MaybeInitiateLazyStop(ds_id, &lazy_heapprofd_, kLazyHeapprofdPropertyName);
  MaybeInitiateLazyStop(ds_id, &lazy_traced_perf_, kLazyTracedPerfPropertyName);

  auto oome_it = java_hprof_oome_instances_.find(ds_id);
  if (oome_it != java_hprof_oome_instances_.end()) {
    java_hprof_oome_instances_.erase(oome_it);
    SetAndroidProperty(kJavaHprofOomActivePropertyName,
                       std::to_string(java_hprof_oome_instances_.size()));
  }
}

void BuiltinProducer::MaybeInitiateLazyStop(DataSourceInstanceID ds_id,
                                            LazyAndroidDaemonState* lazy_state,
                                            const char* prop_name) {
  auto lazy_it = lazy_state->instance_ids.find(ds_id);
  if (lazy_it != lazy_state->instance_ids.end()) {
    lazy_state->instance_ids.erase(lazy_it);

    // if no more sessions - stop daemon after a delay
    if (lazy_state->instance_ids.empty()) {
      uint64_t cur_generation = lazy_state->generation;
      auto weak_this = weak_factory_.GetWeakPtr();
      task_runner_->PostDelayedTask(
          [weak_this, cur_generation, lazy_state, prop_name] {
            if (!weak_this)
              return;
            // |lazy_state| should be valid if the |weak_this| is still valid
            if (lazy_state->generation == cur_generation)
              weak_this->SetAndroidProperty(prop_name, "");
          },
          lazy_state->stop_delay_ms);
    }
  }
}

void BuiltinProducer::Flush(FlushRequestID flush_id,
                            const DataSourceInstanceID* ds_ids,
                            size_t num_ds_ids,
                            FlushFlags) {
  for (size_t i = 0; i < num_ds_ids; i++) {
    auto meta_it = metatrace_.writers.find(ds_ids[i]);
    if (meta_it != metatrace_.writers.end()) {
      meta_it->second.WriteAllAndFlushTraceWriter([] {});
    }
    // nothing to be done for lazy sources
  }
  endpoint_->NotifyFlushComplete(flush_id);
}

bool BuiltinProducer::SetAndroidProperty(const std::string& name,
                                         const std::string& value) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
  return __system_property_set(name.c_str(), value.c_str()) == 0;
#else
  // Allow this to be mocked out for tests on other platforms.
  base::ignore_result(name);
  base::ignore_result(value);
  return true;
#endif
}

}  // namespace perfetto
