// @ts-check

const TOOL_SYSUTIL_TIMELINE = {
  name: 'sysutil-timeline',
  version: '1.0.0',
};

/**
 * @type {import("./docs/jsdocs").Recipe}
 */
const recipe = {
  name: 'timeline',
  title: 'Timeline',
  version: '1.0',
  api_version: '1.0.0',
  experimental: true,
  description:
    'Collects system utilization (CPU, memory, disk, network) over time.',
  deployments: [
    {
      appliesTo: [{ architecture: 'aarch64', os: 'Linux' }],
      dependencies: [
        {
          type: 'tool',
          name: TOOL_SYSUTIL_TIMELINE.name,
          version: TOOL_SYSUTIL_TIMELINE.version,
          requiredWhen: { type: 'always' },
        },
      ],
    },
    {
      appliesTo: [{ architecture: 'x86_64', os: 'Linux' }],
      dependencies: [
        {
          type: 'tool',
          name: TOOL_SYSUTIL_TIMELINE.name,
          version: TOOL_SYSUTIL_TIMELINE.version,
          requiredWhen: { type: 'always' },
        },
      ],
    },
  ],
  parameters: [
    {
      id: 'interval',
      required: false,
      label: 'Interval',
      description: 'Sampling interval in seconds.',
      config: {
        type: 'input',
        defaultValue: '1.0',
      },
    },
    {
      id: 'thread_scan_interval',
      required: false,
      label: 'Thread Scan Interval',
      description: 'Thread scan interval in seconds (defaults to interval).',
      config: {
        type: 'input',
        defaultValue: '1',
      },
    },
  ],
  readyStages: [
    {
      name: 'Checking Timeline recipe is ready',
      description:
        'Check dependencies and parameters specified for sysutil-timeline.',
      exec: readyTimeline,
    },
  ],
  runStages: [
    {
      name: 'Collecting timeline data',
      description: 'Run sysutil-timeline to capture utilization data.',
      exec: runTimeline,
    },
  ],
  renderStages: [
    {
      name: 'Creating render',
      description:
        'Create the renderer specs that are used to produce visualizations',
      exec: renderTimeline,
    },
  ],
};

/**
 * toolStatusToRecipeStatus converts recipe ready advice to a single
 * recipe ready status. The most severe is returned.
 * @param {import("./docs/jsdocs").RecipeReadyAdvice[]} advice
 * @returns {string} readyStatus
 */
function toolStatusToRecipeStatus(advice) {
  const rank = { ready: 0, unknown: 1, warning: 2, error: 3 };
  return advice.reduce(
    (worst, { AdviceSeverity }) =>
      rank[AdviceSeverity] > rank[worst] ? AdviceSeverity : worst,
    'ready',
  );
}

/**
 * generateSysutilTimelineConfig generates a ToolConfigurationsArg for the
 * sysutil-timeline tool integration.
 * @param {import("./docs/jsdocs").Workload} workload
 * @param {Object.<string, any>} params
 * @return {import("./docs/jsdocs").ToolConfigurationsArg}
 */
function generateSysutilTimelineConfig(workload, params) {
  return {
    toolConfigs: [
      {
        name: TOOL_SYSUTIL_TIMELINE.name,
        params: params,
        workload: workload,
        env: {},
      },
    ],
  };
}

/**
 * @param {import("./docs/jsdocs").ReadyExecutionContext} context
 */
function readyTimeline(context) {
  const workload = context.getWorkload();
  const params = {
    interval: context.getParameter('interval'),
    thread_scan_interval: context.getParameter('thread_scan_interval'),
  };

  const tools = generateSysutilTimelineConfig(workload, params);
  const toolResponses = context.probeTools(tools);

  const allAdvice = toolResponses.flatMap((tr, i) =>
    (tr.advice ?? []).map((a) => ({
      ToolName: tools.toolConfigs[i].name,
      AdviceSeverity: a.level,
      MessageCode: a.messageCode,
      Metadata: a.metadata,
      Cause: a.cause,
    })),
  );

  return {
    status: toolStatusToRecipeStatus(allAdvice),
    advice: allAdvice,
  };
}

/**
 * @param {import("./docs/jsdocs").RunExecutionContext} context
 */
function runTimeline(context) {
  const workload = context.getWorkload();
  const params = {
    interval: context.getParameter('interval'),
    thread_scan_interval: context.getParameter('thread_scan_interval'),
  };

  context.runTools(generateSysutilTimelineConfig(workload, params));
}

/**
 * @param {import("./docs/jsdocs").RenderExecutionContext} context
 */
function renderTimeline(context) {
  const TIMELINE_CSV_COMPONENT = 'tool/sysutil-timeline/0/timeline.csv';

  const renderers = [
    {
      type: 'CSV',
      id: 'timeline_csv',
      config: {
        component: TIMELINE_CSV_COMPONENT,
      },
    },
  ];

  const visualizations = [
    {
      type: 'timeline',
      id: 'timeline',
      rendererId: 'timeline_csv',
      title: 'System Utilization Timeline',
      description: '',
      config: {
        xAxisUnit: 's',
        data_source: {
          tables: {
            timeline_total_cpu_mem: [
              { renderer_id: 'timeline_csv', output: 'csv' },
            ],
            timeline_per_core_cpu: [
              { renderer_id: 'timeline_csv', output: 'csv' },
            ],
            timeline_memory_kb: [
              { renderer_id: 'timeline_csv', output: 'csv' },
            ],
            timeline_swap_kb: [{ renderer_id: 'timeline_csv', output: 'csv' }],
            timeline_load_runqueue: [
              { renderer_id: 'timeline_csv', output: 'csv' },
            ],
            timeline_threads_ctx_irq_faults: [
              { renderer_id: 'timeline_csv', output: 'csv' },
            ],
            timeline_disk_iops: [
              { renderer_id: 'timeline_csv', output: 'csv' },
            ],
            timeline_disk_bw: [{ renderer_id: 'timeline_csv', output: 'csv' }],
            timeline_net_bw: [{ renderer_id: 'timeline_csv', output: 'csv' }],
          },
        },
        groups: {
          timeline_total_cpu_mem: {
            type: 'line',
            index: 0,
            config: {
              xAxisTitle: 'Time (s)',
              yAxisTitle: 'Utilization (%)',
              customQuery: {
                tableNamePlaceholder: '{table}',
                query:
                  'SELECT ' +
                  'try_cast("uptime_s" AS DOUBLE) AS "uptime_s", ' +
                  'try_cast("cpu_total_percent" AS DOUBLE) AS "cpu_total_percent", ' +
                  'try_cast("mem_used_percent" AS DOUBLE) AS "mem_used_percent" ' +
                  'FROM {table}',
              },
              series: [
                {
                  type: 'single',
                  name: 'CPU Use',
                  xColumn: 'uptime_s',
                  yColumn: 'cpu_total_percent',
                },
                {
                  type: 'single',
                  name: 'Mem Use',
                  xColumn: 'uptime_s',
                  yColumn: 'mem_used_percent',
                },
              ],
            },
          },
          timeline_per_core_cpu: {
            type: 'line',
            index: 1,
            config: {
              xAxisTitle: 'Time (s)',
              yAxisTitle: 'CPU Utilization (%)',
              customQuery: {
                tableNamePlaceholder: '{table}',
                query:
                  'SELECT ' +
                  'try_cast("uptime_s" AS DOUBLE) AS "uptime_s", ' +
                  "try_cast(COLUMNS('^cpu\\d+_percent$') AS DOUBLE) " +
                  'FROM {table}',
              },
              series: [
                {
                  type: 'pattern',
                  xColumn: 'uptime_s',
                  yColumn: { pattern: '^cpu(\\d+)_percent$' },
                  name: { template: 'Core {y1}' },
                },
              ],
            },
          },
          timeline_memory_kb: {
            type: 'line',
            index: 2,
            config: {
              xAxisTitle: 'Time (s)',
              yAxisTitle: 'kB',
              customQuery: {
                tableNamePlaceholder: '{table}',
                query:
                  'SELECT ' +
                  'try_cast("uptime_s" AS DOUBLE) AS "uptime_s", ' +
                  'try_cast("mem_total_kb" AS DOUBLE) AS "mem_total_kb", ' +
                  'try_cast("mem_available_kb" AS DOUBLE) AS "mem_available_kb", ' +
                  'try_cast("mem_used_kb" AS DOUBLE) AS "mem_used_kb" ' +
                  'FROM {table}',
              },
              series: [
                {
                  type: 'single',
                  name: 'Mem Total',
                  xColumn: 'uptime_s',
                  yColumn: 'mem_total_kb',
                },
                {
                  type: 'single',
                  name: 'Mem Available',
                  xColumn: 'uptime_s',
                  yColumn: 'mem_available_kb',
                },
                {
                  type: 'single',
                  name: 'Mem Used',
                  xColumn: 'uptime_s',
                  yColumn: 'mem_used_kb',
                },
              ],
            },
          },
          timeline_swap_kb: {
            type: 'line',
            index: 3,
            config: {
              xAxisTitle: 'Time (s)',
              yAxisTitle: 'kB',
              customQuery: {
                tableNamePlaceholder: '{table}',
                query:
                  'SELECT ' +
                  'try_cast("uptime_s" AS DOUBLE) AS "uptime_s", ' +
                  'try_cast("swap_total_kb" AS DOUBLE) AS "swap_total_kb", ' +
                  'try_cast("swap_used_kb" AS DOUBLE) AS "swap_used_kb" ' +
                  'FROM {table}',
              },
              series: [
                {
                  type: 'single',
                  name: 'Swap Total',
                  xColumn: 'uptime_s',
                  yColumn: 'swap_total_kb',
                },
                {
                  type: 'single',
                  name: 'Swap Used',
                  xColumn: 'uptime_s',
                  yColumn: 'swap_used_kb',
                },
              ],
            },
          },
          timeline_load_runqueue: {
            type: 'line',
            index: 4,
            config: {
              xAxisTitle: 'Time (s)',
              yAxisTitle: 'Value',
              customQuery: {
                tableNamePlaceholder: '{table}',
                query:
                  'SELECT ' +
                  'try_cast("uptime_s" AS DOUBLE) AS "uptime_s", ' +
                  'try_cast("load1" AS DOUBLE) AS "load1", ' +
                  'try_cast("load5" AS DOUBLE) AS "load5", ' +
                  'try_cast("load15" AS DOUBLE) AS "load15", ' +
                  'try_cast("procs_running" AS DOUBLE) AS "procs_running", ' +
                  'try_cast("procs_blocked" AS DOUBLE) AS "procs_blocked" ' +
                  'FROM {table}',
              },
              series: [
                {
                  type: 'single',
                  name: 'Load 1',
                  xColumn: 'uptime_s',
                  yColumn: 'load1',
                },
                {
                  type: 'single',
                  name: 'Load 5',
                  xColumn: 'uptime_s',
                  yColumn: 'load5',
                },
                {
                  type: 'single',
                  name: 'Load 15',
                  xColumn: 'uptime_s',
                  yColumn: 'load15',
                },
                {
                  type: 'single',
                  name: 'Procs Running',
                  xColumn: 'uptime_s',
                  yColumn: 'procs_running',
                },
                {
                  type: 'single',
                  name: 'Procs Blocked',
                  xColumn: 'uptime_s',
                  yColumn: 'procs_blocked',
                },
              ],
            },
          },
          timeline_threads_ctx_irq_faults: {
            type: 'line',
            index: 5,
            config: {
              xAxisTitle: 'Time (s)',
              yAxisTitle: 'Rate / Count',
              customQuery: {
                tableNamePlaceholder: '{table}',
                query:
                  'SELECT ' +
                  'try_cast("uptime_s" AS DOUBLE) AS "uptime_s", ' +
                  'try_cast("threads_total" AS DOUBLE) AS "threads_total", ' +
                  'try_cast("ctxt_per_s" AS DOUBLE) AS "ctxt_per_s", ' +
                  'try_cast("intr_per_s" AS DOUBLE) AS "intr_per_s", ' +
                  'try_cast("page_faults_per_s" AS DOUBLE) AS "page_faults_per_s", ' +
                  'try_cast("pgmajfaults_per_s" AS DOUBLE) AS "pgmajfaults_per_s" ' +
                  'FROM {table}',
              },
              series: [
                {
                  type: 'single',
                  name: 'Threads',
                  xColumn: 'uptime_s',
                  yColumn: 'threads_total',
                },
                {
                  type: 'single',
                  name: 'Context Switches/s',
                  xColumn: 'uptime_s',
                  yColumn: 'ctxt_per_s',
                },
                {
                  type: 'single',
                  name: 'IRQs/s',
                  xColumn: 'uptime_s',
                  yColumn: 'intr_per_s',
                },
                {
                  type: 'single',
                  name: 'Page Faults/s',
                  xColumn: 'uptime_s',
                  yColumn: 'page_faults_per_s',
                },
                {
                  type: 'single',
                  name: 'Major Faults/s',
                  xColumn: 'uptime_s',
                  yColumn: 'pgmajfaults_per_s',
                },
              ],
            },
          },
          timeline_disk_iops: {
            type: 'line',
            index: 6,
            config: {
              xAxisTitle: 'Time (s)',
              yAxisTitle: 'IOPS',
              customQuery: {
                tableNamePlaceholder: '{table}',
                query:
                  'SELECT ' +
                  'try_cast("uptime_s" AS DOUBLE) AS "uptime_s", ' +
                  "try_cast(COLUMNS('^read_iops_.*$') AS DOUBLE), " +
                  "try_cast(COLUMNS('^write_iops_.*$') AS DOUBLE) " +
                  'FROM {table}',
              },
              series: [
                {
                  type: 'pattern',
                  xColumn: 'uptime_s',
                  yColumn: { pattern: '^read_iops_(.+)$' },
                  name: { template: 'Read IOPS {y1}' },
                },
                {
                  type: 'pattern',
                  xColumn: 'uptime_s',
                  yColumn: { pattern: '^write_iops_(.+)$' },
                  name: { template: 'Write IOPS {y1}' },
                },
              ],
            },
          },
          timeline_disk_bw: {
            type: 'line',
            index: 7,
            config: {
              xAxisTitle: 'Time (s)',
              yAxisTitle: 'Bytes/s',
              customQuery: {
                tableNamePlaceholder: '{table}',
                query:
                  'SELECT ' +
                  'try_cast("uptime_s" AS DOUBLE) AS "uptime_s", ' +
                  "try_cast(COLUMNS('^read_bps_.*$') AS DOUBLE), " +
                  "try_cast(COLUMNS('^write_bps_.*$') AS DOUBLE) " +
                  'FROM {table}',
              },
              series: [
                {
                  type: 'pattern',
                  xColumn: 'uptime_s',
                  yColumn: { pattern: '^read_bps_(.+)$' },
                  name: { template: 'Read Bps {y1}' },
                },
                {
                  type: 'pattern',
                  xColumn: 'uptime_s',
                  yColumn: { pattern: '^write_bps_(.+)$' },
                  name: { template: 'Write Bps {y1}' },
                },
              ],
            },
          },
          timeline_net_bw: {
            type: 'line',
            index: 8,
            config: {
              xAxisTitle: 'Time (s)',
              yAxisTitle: 'Bytes/s',
              customQuery: {
                tableNamePlaceholder: '{table}',
                query:
                  'SELECT ' +
                  'try_cast("uptime_s" AS DOUBLE) AS "uptime_s", ' +
                  "try_cast(COLUMNS('^rx_bps_.*$') AS DOUBLE), " +
                  "try_cast(COLUMNS('^tx_bps_.*$') AS DOUBLE) " +
                  'FROM {table}',
              },
              series: [
                {
                  type: 'pattern',
                  xColumn: 'uptime_s',
                  yColumn: { pattern: '^rx_bps_(.+)$' },
                  name: { template: 'RX {y1}' },
                },
                {
                  type: 'pattern',
                  xColumn: 'uptime_s',
                  yColumn: { pattern: '^tx_bps_(.+)$' },
                  name: { template: 'TX {y1}' },
                },
              ],
            },
          },
        },
      },
    },
  ];

  return { renderers, visualizations };
}
