// Copyright (C) 2023 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.

import {addDebugSliceTrack} from '../../components/tracks/debug_tracks';
import {Trace} from '../../public/trace';
import {PerfettoPlugin} from '../../public/plugin';
import {getTimeSpanOfSelectionOrVisibleWindow} from '../../public/utils';
import {addQueryResultsTab} from '../../components/query_table/query_result_tab';

export default class implements PerfettoPlugin {
  static readonly id = 'dev.perfetto.AndroidPerf';
  async addAppProcessStartsDebugTrack(
    ctx: Trace,
    reason: string,
    sliceName: string,
  ): Promise<void> {
    const sliceColumns = [
      'id',
      'ts',
      'dur',
      'reason',
      'process_name',
      'intent',
      'table_name',
    ];
    await addDebugSliceTrack({
      trace: ctx,
      data: {
        sqlSource: `
                    SELECT
                      start_id AS id,
                      proc_start_ts AS ts,
                      total_dur AS dur,
                      reason,
                      process_name,
                      intent,
                      'slice' AS table_name
                    FROM android_app_process_starts
                    WHERE reason = '${reason}'
                 `,
        columns: sliceColumns,
      },
      title: 'app_' + sliceName + '_start reason: ' + reason,
      argColumns: sliceColumns,
    });
  }

  async onTraceLoad(ctx: Trace): Promise<void> {
    ctx.commands.registerCommand({
      id: 'dev.perfetto.AndroidPerf#BinderSystemServerIncoming',
      name: 'Run query: system_server incoming binder graph',
      callback: () =>
        addQueryResultsTab(ctx, {
          query: `INCLUDE PERFETTO MODULE android.binder;
           SELECT * FROM android_binder_incoming_graph((SELECT upid FROM process WHERE name = 'system_server'))`,
          title: 'system_server incoming binder graph',
        }),
    });

    ctx.commands.registerCommand({
      id: 'dev.perfetto.AndroidPerf#BinderSystemServerOutgoing',
      name: 'Run query: system_server outgoing binder graph',
      callback: () =>
        addQueryResultsTab(ctx, {
          query: `INCLUDE PERFETTO MODULE android.binder;
           SELECT * FROM android_binder_outgoing_graph((SELECT upid FROM process WHERE name = 'system_server'))`,
          title: 'system_server outgoing binder graph',
        }),
    });

    ctx.commands.registerCommand({
      id: 'dev.perfetto.AndroidPerf#MonitorContentionSystemServer',
      name: 'Run query: system_server monitor_contention graph',
      callback: () =>
        addQueryResultsTab(ctx, {
          query: `INCLUDE PERFETTO MODULE android.monitor_contention;
           SELECT * FROM android_monitor_contention_graph((SELECT upid FROM process WHERE name = 'system_server'))`,
          title: 'system_server monitor_contention graph',
        }),
    });

    ctx.commands.registerCommand({
      id: 'dev.perfetto.AndroidPerf#BinderAll',
      name: 'Run query: all process binder graph',
      callback: () =>
        addQueryResultsTab(ctx, {
          query: `INCLUDE PERFETTO MODULE android.binder;
           SELECT * FROM android_binder_graph(-1000, 1000, -1000, 1000)`,
          title: 'all process binder graph',
        }),
    });

    ctx.commands.registerCommand({
      id: 'dev.perfetto.AndroidPerf#ThreadClusterDistribution',
      name: 'Run query: runtime cluster distribution for a thread',
      callback: async (tid) => {
        if (tid === undefined) {
          tid = prompt('Enter a thread tid', '');
          if (tid === null) return;
        }
        addQueryResultsTab(ctx, {
          query: `
          INCLUDE PERFETTO MODULE android.cpu.cluster_type;
          WITH
            total_runtime AS (
              SELECT sum(dur) AS total_runtime
              FROM sched s
              LEFT JOIN thread t
                USING (utid)
              WHERE t.tid = ${tid}
            )
            SELECT
              c.cluster_type AS cluster, sum(dur)/1e6 AS total_dur_ms,
              sum(dur) * 1.0 / (SELECT * FROM total_runtime) AS percentage
            FROM sched s
            LEFT JOIN thread t
              USING (utid)
            LEFT JOIN android_cpu_cluster_mapping c
              USING (cpu)
            WHERE t.tid = ${tid}
            GROUP BY 1`,
          title: `runtime cluster distrubtion for tid ${tid}`,
        });
      },
    });

    ctx.commands.registerCommand({
      id: 'dev.perfetto.AndroidPerf#SchedLatency',
      name: 'Run query: top 50 sched latency for a thread',
      callback: async (tid) => {
        if (tid === undefined) {
          tid = prompt('Enter a thread tid', '');
          if (tid === null) return;
        }
        addQueryResultsTab(ctx, {
          query: `
          SELECT ts.*, t.tid, t.name, tt.id AS track_id
          FROM thread_state ts
          LEFT JOIN thread_track tt
           USING (utid)
          LEFT JOIN thread t
           USING (utid)
          WHERE ts.state IN ('R', 'R+') AND tid = ${tid}
           ORDER BY dur DESC
          LIMIT 50`,
          title: `top 50 sched latency slice for tid ${tid}`,
        });
      },
    });

    ctx.commands.registerCommand({
      id: 'dev.perfetto.AndroidPerf#SchedLatencyInSelectedWindow',
      name: 'Top 50 sched latency in selected time window',
      callback: async () => {
        const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx);
        addQueryResultsTab(ctx, {
          title: 'top 50 sched latency slice in selcted time window',
          query: `SELECT
            ts.*,
            t.tid,
            t.name AS thread_name,
            tt.id AS track_id,
            p.name AS process_name
          FROM thread_state ts
          LEFT JOIN thread_track tt
           USING (utid)
          LEFT JOIN thread t
           USING (utid)
          LEFT JOIN process p
           USING (upid)
          WHERE ts.state IN ('R', 'R+')
           AND ts.ts >= ${window.start} and ts.ts < ${window.end}
          ORDER BY dur DESC
          LIMIT 50`,
        });
      },
    });

    ctx.commands.registerCommand({
      id: 'dev.perfetto.AndroidPerf#AppProcessStarts',
      name: 'Add tracks: app process starts',
      callback: async () => {
        await ctx.engine.query(
          `INCLUDE PERFETTO MODULE android.app_process_starts;`,
        );

        const startReason = ['activity', 'service', 'broadcast', 'provider'];
        for (const reason of startReason) {
          await this.addAppProcessStartsDebugTrack(ctx, reason, 'process_name');
        }
      },
    });

    ctx.commands.registerCommand({
      id: 'dev.perfetto.AndroidPerf#AppIntentStarts',
      name: 'Add tracks: app intent starts',
      callback: async () => {
        await ctx.engine.query(
          `INCLUDE PERFETTO MODULE android.app_process_starts;`,
        );

        const startReason = ['activity', 'service', 'broadcast'];
        for (const reason of startReason) {
          await this.addAppProcessStartsDebugTrack(ctx, reason, 'intent');
        }
      },
    });
  }
}
