// JSON tapset.
// Copyright (C) 2015 Red Hat Inc.
//
// This file is part of systemtap, and is free software.  You can
// redistribute it and/or modify it under the terms of the GNU General
// Public License (GPL); either version 2, or (at your option) any
// later version.
//
// <tapsetdescription>
// The JSON tapset provides probes, functions, and macros to generate
// a JSON metadata and data file. The JSON metadata file is located in
// /proc/systemtap/MODULE/metadata.json. The JSON data file is located
// in /proc/systemtap/MODULE/data.json. The JSON data file is updated
// with current data every time the file is read.
// </tapsetdescription>


@__private30 global __json_prefix
@__private30 global __json_metric_type, __json_metric_desc
@__private30 global __json_metric_units, __json_metric_pointer
@__private30 global __json_array_metric_type, __json_array_metric_desc
@__private30 global __json_array_metric_units, __json_array_metric_pointer
global __json_metric_output, __json_array_output

probe init
{
  // Make sure the systemtap translator knows what types the globals are.
  __json_prefix = ""

  __json_metric_units[""] = ""
  delete __json_metric_units

  __json_array_metric_type["", ""] = ""
  delete __json_array_metric_type

  __json_array_metric_desc["", ""] = ""
  delete __json_array_metric_desc

  __json_array_metric_units["", ""] = ""
  delete __json_array_metric_units

  __json_array_metric_pointer["", ""] = ""
  delete __json_array_metric_pointer

  __json_array_output[""] = 1
  delete __json_array_output
}

/**
 * sfunction json_set_prefix - Set the metric prefix.
 *
 * @prefix: The prefix name to be used.
 *
 * Description: This function sets the "prefix", which is the name
 * of the base of the metric hierarchy. Calling this function is
 * optional, by default the name of the systemtap module is used.
 */
function json_set_prefix:long(prefix:string)
{
  __json_prefix = prefix
}

/**
 * sfunction json_add_numeric_metric - Add a numeric metric
 *
 * @name: The name of the numeric metric.
 * @description: Metric description. An empty string can be used.
 * @units: Metic units. An empty string can be used.
 *
 * Description: This function adds a numeric metric, setting up
 * everything needed.
 */
function json_add_numeric_metric:long(name:string, description:string, units:string)
{
  if (name in __json_metric_type)
    error(sprintf("Metric '%s' already exists", name))
  __json_metric_type[name] = "integer"
  __json_metric_desc[name] = description
  __json_metric_units[name] = units
  # FIXME: Do we need to validate the name? For instance, what if it
  # had a double-quote character in it?
  __json_metric_pointer[name] = "/" . name
  return 0
}

/**
 * sfunction json_add_string_metric - Add a string metric
 *
 * @name: The name of the string metric.
 * @description: Metric description. An empty string can be used.
 *
 * Description: This function adds a string metric, setting up
 * everything needed.
 */
function json_add_string_metric:long(name:string, description:string)
{
  if (name in __json_metric_type)
    error(sprintf("Metric '%s' already exists", name))
  __json_metric_type[name] = "string"
  __json_metric_desc[name] = description
  __json_metric_pointer[name] = "/" . name
  return 0
}

/**
 * sfunction json_add_array - Add an array
 *
 * @name: The name of the array.
 * @description: Array description. An empty string can be used.
 *
 * Description: This function adds a array, setting up everything
 * needed. Arrays contain other metrics, added with
 * json_add_array_numeric_metric() or json_add_array_string_metric().
 */
function json_add_array:long(name:string, description:string)
{
  if (name in __json_metric_type)
    error(sprintf("Metric '%s' already exists", name))
  __json_metric_type[name] = "array"
  __json_metric_desc[name] = description
  __json_metric_pointer[name] = "/" . name

  # Go ahead and add "__id", which is the array index.
  json_add_array_string_metric(name, "__id", "")
  return 0
}

/**
 * sfunction json_add_array_numeric_metric - Add a numeric metric to an array
 *
 * @array_name: The name of the array the numeric metric should be
 * added to.
 * @metric_name: The name of the numeric metric.
 * @metric_description: Metric description. An empty string can be used.
 * @metric_units: Metic units. An empty string can be used.
 *
 * Description: This function adds a numeric metric to an array,
 * setting up everything needed.
 */
function json_add_array_numeric_metric:long(array_name:string, metric_name:string, metric_description:string, metric_units:string)
{
  if ([array_name, metric_name] in __json_array_metric_type)
    error(sprintf("Array metric '%s' already exists in array %s", metric_name,
		  array_name))
  __json_array_metric_pointer[array_name, metric_name]
    = sprintf("/%s", metric_name)
  __json_array_metric_type[array_name, metric_name] = "integer"
  __json_array_metric_desc[array_name, metric_name] = metric_description
  __json_array_metric_units[array_name, metric_name] = metric_units
  return 0
}

/**
 * sfunction json_add_array_string_metric - Add a string metric to an array
 *
 * @array_name: The name of the array the string metric should be
 * added to.
 * @metric_name: The name of the string metric.
 * @metric_description: Metric description. An empty string can be used.
 *
 * Description: This function adds a string metric to an array,
 * setting up everything needed.
 */
function json_add_array_string_metric:long(array_name:string, metric_name:string, metric_description:string)
{
  if ([array_name, metric_name] in __json_array_metric_type)
    error(sprintf("Array metric '%s' already exists in array %s", metric_name,
		  array_name))
  __json_array_metric_pointer[array_name, metric_name]
    = sprintf("/%s", metric_name)
  __json_array_metric_type[array_name, metric_name] = "string"
  __json_array_metric_desc[array_name, metric_name] = metric_description
  return 0
}

probe procfs("metadata.json").read.maxsize(8192)
{
  # Note: This is the "pretty-printed" version, intended to be read by
  # humans. We could remove the whitespace and newlines if we wanted
  # to make the output shorter (but less readable).
  #
  # Note 2: Note that we have to break this long string into more than
  # 1 assignment since we're bumping up against MAXSTRINGLEN. Procfs
  # $value can hold more than MAXSTRINGLEN because of the
  # '.maxsize(N)' parameter.
  $value = "{\n"

  if (__json_prefix != "")
    $value .= sprintf("  \"prefix\": \"%s\",\n", __json_prefix)

  $value .= "  \"metrics\": [\n"

  __comma_needed = 0
  foreach (__name in __json_metric_type) {
    if (__comma_needed)
      $value .= ",\n"
    __comma_needed = 1    

    if (__json_metric_type[__name] != "array") {
      @__json_output_metric_metadata("    ", __name,
				     __json_metric_pointer[__name],
				     __json_metric_type[__name],
				     __json_metric_desc[__name],
				     __json_metric_units[__name])
    }
    else {
      # For an array, we have to output the information about the
      # array itself, then output each field in the array.
      $value .= "    \{\n"
      $value .= sprintf("      \"name\": \"%s\",\n", __name)
      $value .= sprintf("      \"pointer\": \"%s\",\n",
			__json_metric_pointer[__name])
      $value .= "      \"type\": \"array\",\n"
      if (strlen(__json_metric_desc[__name]) > 0)
        $value .= sprintf("      \"description\": \"%s\",\n", 
			  __json_metric_desc[__name])
      $value .= "      \"index\": \"/__id\",\n"
      $value .= "      \"metrics\": [\n"
      __array_comma_needed = 0

      # Output each field in the array.
      foreach ([__array_name, __metric_name] in __json_array_metric_type) {
        if (__name == __array_name) {
          # Skip '__id', the array index.
          if (__metric_name == "__id")
            continue
    
          if (__array_comma_needed)
            $value .= ",\n"
          __array_comma_needed = 1    
    
          __subpointer = __json_array_metric_pointer[__array_name, __metric_name]
          __subtype = __json_array_metric_type[__array_name, __metric_name]
          __subdesc = __json_array_metric_desc[__array_name, __metric_name]
	  __subunits = __json_array_metric_units[__array_name, __metric_name]
    
          @__json_output_metric_metadata("        ", __metric_name,
					 __subpointer, __subtype,
					 __subdesc, __subunits)
        }
      }
      $value .=
        "\n"
	"      ]\n"
	"    }"
    }
  }

  $value .=
    "\n"
    "  ]\n"
    "}\n"
}

/**
 * probe json_data - Fires whenever JSON data is wanted by a reader.
 *
 * Context:
 *  This probe fires when the JSON data is about to be read. This
 *  probe must gather up data and then call the following macros to
 *  output the data in JSON format. First, @json_output_data_start()
 *  must be called. That call is followed by one or more of the
 *  following (one call for each data item):
 *  @json_output_string_value(), @json_output_numeric_value(),
 *  @json_output_array_string_value(), and
 *  @json_output_array_numeric_value(). Finally @json_output_data_end()
 *  must be called. 
 */
probe json_data = procfs("data.json").read.maxsize(8192)
{
}
