/* SPDX-License-Identifier: Apache-2.0
 * Copyright(c) 2022 Cisco Systems, Inc.
 */

#include <vlib/vlib.h>
#include <vlib/unix/unix.h>
#include <vlib/stats/stats.h>

enum
{
  NODE_CLOCKS,
  NODE_VECTORS,
  NODE_CALLS,
  NODE_SUSPENDS,
  N_NODE_COUNTERS
};

struct
{
  u32 entry_index;
  char *name;
} node_counters[] = {
  [NODE_CLOCKS] = { .name = "clocks" },
  [NODE_VECTORS] = { .name = "vectors" },
  [NODE_CALLS] = { .name = "calls" },
  [NODE_SUSPENDS] = { .name = "suspends" },
};

static struct
{
  u8 *name;
  u32 symlinks[N_NODE_COUNTERS];
} *node_data = 0;

static vlib_stats_string_vector_t node_names = 0;

static inline void
update_node_counters (vlib_stats_segment_t *sm)
{
  clib_bitmap_t *bmp = 0;
  vlib_main_t **stat_vms = 0;
  vlib_node_t ***node_dups = 0;
  u32 n_nodes;
  int i, j;

  vlib_node_get_nodes (0 /* vm, for barrier sync */,
		       (u32) ~0 /* all threads */, 1 /* include stats */,
		       0 /* barrier sync */, &node_dups, &stat_vms);

  n_nodes = vec_len (node_dups[0]);

  vec_validate (node_data, n_nodes - 1);

  for (i = 0; i < n_nodes; i++)
    if (vec_is_equal (node_data[i].name, node_dups[0][i]->name) == 0)
      bmp = clib_bitmap_set (bmp, i, 1);

  if (bmp)
    {
      u32 last_thread = vlib_get_n_threads ();
      vlib_stats_segment_lock ();
      clib_bitmap_foreach (i, bmp)
	{
	  if (node_data[i].name)
	    {
	      vec_free (node_data[i].name);
	      for (j = 0; j < ARRAY_LEN (node_data->symlinks); j++)
		vlib_stats_remove_entry (node_data[i].symlinks[j]);
	    }
	}
      /* We can't merge the loops because a node index corresponding to a given
       * node name can change between 2 updates. Otherwise, we could add
       * already existing symlinks or delete valid ones.
       */
      clib_bitmap_foreach (i, bmp)
	{
	  vlib_node_t *n = node_dups[0][i];
	  node_data[i].name = vec_dup (n->name);
	  vlib_stats_set_string_vector (&node_names, n->index, "%v", n->name);

	  for (int j = 0; j < ARRAY_LEN (node_counters); j++)
	    {
	      vlib_stats_validate (node_counters[j].entry_index, last_thread,
				   n_nodes - 1);
	      node_data[i].symlinks[j] = vlib_stats_add_symlink (
		node_counters[j].entry_index, n->index, "/nodes/%U/%s",
		format_vlib_stats_symlink, n->name, node_counters[j].name);
	      ASSERT (node_data[i].symlinks[j] != CLIB_U32_MAX);
	    }
	}
      vlib_stats_segment_unlock ();
      vec_free (bmp);
    }

  for (j = 0; j < vec_len (node_dups); j++)
    {
      vlib_node_t **nodes = node_dups[j];

      for (i = 0; i < vec_len (nodes); i++)
	{
	  counter_t **counters;
	  counter_t *c;
	  vlib_node_t *n = nodes[i];

	  counters = vlib_stats_get_entry_data_pointer (
	    node_counters[NODE_CLOCKS].entry_index);
	  c = counters[j];
	  c[n->index] = n->stats_total.clocks - n->stats_last_clear.clocks;

	  counters = vlib_stats_get_entry_data_pointer (
	    node_counters[NODE_VECTORS].entry_index);
	  c = counters[j];
	  c[n->index] = n->stats_total.vectors - n->stats_last_clear.vectors;

	  counters = vlib_stats_get_entry_data_pointer (
	    node_counters[NODE_CALLS].entry_index);
	  c = counters[j];
	  c[n->index] = n->stats_total.calls - n->stats_last_clear.calls;

	  counters = vlib_stats_get_entry_data_pointer (
	    node_counters[NODE_SUSPENDS].entry_index);
	  c = counters[j];
	  c[n->index] = n->stats_total.suspends - n->stats_last_clear.suspends;
	}
      vec_free (node_dups[j]);
    }
  vec_free (node_dups);
  vec_free (stat_vms);
}

static void
do_stat_segment_updates (vlib_main_t *vm, vlib_stats_segment_t *sm)
{
  if (sm->node_counters_enabled)
    update_node_counters (sm);

  vlib_stats_collector_t *c;
  pool_foreach (c, sm->collectors)
    {
      vlib_stats_collector_data_t data = {
	.entry_index = c->entry_index,
	.vector_index = c->vector_index,
	.private_data = c->private_data,
	.entry = sm->directory_vector + c->entry_index,
      };
      c->fn (&data);
    }

  /* Heartbeat, so clients detect we're still here */
  sm->directory_vector[STAT_COUNTER_HEARTBEAT].value++;
}

static uword
stat_segment_collector_process (vlib_main_t *vm, vlib_node_runtime_t *rt,
				vlib_frame_t *f)
{
  vlib_stats_segment_t *sm = vlib_stats_get_segment ();

  if (sm->node_counters_enabled)
    {
      node_names = vlib_stats_add_string_vector ("/sys/node/names");
      ASSERT (node_names);

      for (int x = 0; x < ARRAY_LEN (node_counters); x++)
	{
	  node_counters[x].entry_index = vlib_stats_add_counter_vector (
	    "/sys/node/%s", node_counters[x].name);
	  ASSERT (node_counters[x].entry_index != CLIB_U32_MAX);
	}
    }

  sm->directory_vector[STAT_COUNTER_BOOTTIME].value = unix_time_now ();

  while (1)
    {
      do_stat_segment_updates (vm, sm);
      vlib_process_suspend (vm, sm->update_interval);
    }
  return 0; /* or not */
}

VLIB_REGISTER_NODE (stat_segment_collector, static) = {
  .function = stat_segment_collector_process,
  .name = "statseg-collector-process",
  .type = VLIB_NODE_TYPE_PROCESS,
};