C exit program to log information using a data queue

This Query Supervisor exit program shows how to use an asynchronous job to log information about the query that reported a threshold.

This is the registered exit program. It sends the exit program input data to a data queue named SUPERVISOR/SUPER_DQ for further processing by an SQL procedure. The data is sent as a JSON object, encoded as UTF-16 data.

To compile this C program, use the following commands. By using USRPRF(*OWNER), *PUBLIC does not need to be given access to the objects used by this program. The owning profile of the created program must have authority to the commands and objects used by the exit program.

CRTCMOD MODULE(SUPERVISOR/SEND_JSON) SRCFILE(SUPERVISOR/QCSRC) LOCALETYPE(*LOCALEUCS2) 

CRTPGM  PGM(SUPERVISOR/SEND_JSON) USRPRF(*OWNER) ACTGRP(*CALLER)
The program can be registered as an exit program using this command:
ADDEXITPGM EXITPNT(QIBM_QQQ_QRY_SUPER) FORMAT(QRYS0100) PGMNBR(*LOW) PGM(SUPERVISOR/SEND_JSON) 
THDSAFE(*YES) TEXT('Query Supervisor logging with data queue')
#include <except.h>
#include <decimal.h>
#include <qsnddtaq.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#include <iconv.h>
#include <assert.h>
#include <time.h>
#include <qp0ztrc.h>
#include <eqqqrysv.h>

/* Properties of the data queue that  we will send the JSON to: */
char* DataQLib =  "SUPERVISOR";
char* DataQName = "SUPER_DQ  ";
const int DataQMaxBytes = 45000;

/* Define a lookup table that indicates how to handle escapable JSON
    characters. Each entry represents one of the first 160 UTF-16
    codepoints.                                                      */
enum EscapeAction
  {None,UTF16,Backspace,Tab,Newline,Formfeed,CR,Quote,Backslash};
char JsonEscapeAction[160]={
/*x00*/ UTF16,    UTF16,    UTF16,    UTF16,
        UTF16,    UTF16,    UTF16,    UTF16,
        Backspace,Tab,      Newline,  UTF16,
        Formfeed, CR,       UTF16,    UTF16,
/*x10*/ UTF16,    UTF16,    UTF16,    UTF16,
        UTF16,    UTF16,    UTF16,    UTF16,
        UTF16,    UTF16,    UTF16,    UTF16,
        UTF16,    UTF16,    UTF16,    UTF16,
/*x20*/ None,     None,     Quote,    None,
        None,     None,     None,     None,
        None,     None,     None,     None,
        None,     None,     None,     None,
/*x30*/ None,     None,     None,     None,
        None,     None,     None,     None,
        None,     None,     None,     None,
        None,     None,     None,     None,
/*x40*/ None,     None,     None,     None,
        None,     None,     None,     None,
        None,     None,     None,     None,
        None,     None,     None,     None,
/*x50*/ None,     None,     None,     None,
        None,     None,     None,     None,
        None,     None,     None,     None,
        Backslash,None,     None,     None,
/*x60*/ None,     None,     None,     None,
        None,     None,     None,     None,
        None,     None,     None,     None,
        None,     None,     None,     None,
/*x70*/ None,     None,     None,     None,
        None,     None,     None,     None,
        None,     None,     None,     None,
        None,     None,     None,     None,
/*x80*/ UTF16,    UTF16,    UTF16,    UTF16,
        UTF16,    UTF16,    UTF16,    UTF16,
        UTF16,    UTF16,    UTF16,    UTF16,
        UTF16,    UTF16,    UTF16,    UTF16,
/*x90*/ UTF16,    UTF16,    UTF16,    UTF16,
        UTF16,    UTF16,    UTF16,    UTF16,
        UTF16,    UTF16,    UTF16,    UTF16,
        UTF16,    UTF16,    UTF16,    UTF16
};

/* Define constants used for generating JSON escape sequences. */
const wchar_t HexChars[17]=L"0123456789ABCDEF";
const wchar_t EscapeChars[10]= L"  btnfr\"\\";
const wchar_t UnicodePrefix[4]= L"u00";

/* Copies a UTF-16 source stream to a UTF-16 target stream, escaping
    JSON characters as needed.                                       */
void json_escape(wchar_t** target, int target_length,
                 const wchar_t* source, int source_length)
{
  const wchar_t** target_end = target + target_length;

  /* Loop through all the source UTF-16 characters, copying any that
     do not require escaping, and escaping those that are required. */
  while (source_length-- && target < target_end)
  {
    const wchar_t c = *source;
    enum EscapeAction action;

    /* Is the current character in the defined escapable range? */
    if(c<(sizeof(JsonEscapeAction)/sizeof(JsonEscapeAction[0])))
        action = (enum EscapeAction)JsonEscapeAction[c];
    else
        action = None;

    if (action == None)
    {
      /* No escaping needed. Emit the source character. */
      **target = c;
      ++*target;
    }
    else
    {
        **target = EscapeChars[Backslash];
        ++*target;
        if (action == UTF16)
        {
          if (target+6 >= target_end)
           break;

          /* We  escape this as a UTF16 codepoint: \u00XX */
          memcpy(*target, UnicodePrefix, sizeof(UnicodePrefix)-2);
          *target += sizeof(UnicodePrefix)/2-1;

          **target = HexChars[c >> 4];
          ++*target;

          **target = HexChars[c & 0x0f];
          ++*target;
        }
        else
        {
           if (target >= target_end)
            break;

          /* We escape this as a special character: e.g. \t   */
          **target = EscapeChars[action];
          ++*target;
        }
    }
    ++source;
  }
}

/* Uses iconv to convert an input string in Job CCSID to UTF-16. */
size_t convert_to_utf16(const char* input, size_t input_bytes,
                        wchar_t** output, size_t output_bytes,
                        iconv_t converter)
{
  int iconv_rc;
  int original_output_bytes = output_bytes;
  iconv_rc = iconv(converter,
                   &input, &input_bytes,
                   (char**)output, &output_bytes);

  /* If conversion was successful, we'll return the number of bytes that
     were produced. */
  if (iconv_rc >= 0)
    return original_output_bytes - output_bytes;

  /* Conversion was unsuccessful. Put an ERROR value in the output if
     there is room. */
  if (original_output_bytes >= 10)
  {
    memcpy(*output, L"ERROR", 10);
    *output+=5;
    return 10;
  }

  return 0;
}

/* Returns the trimmed length of the given wide string after
   discarding any trailing blanks. */
size_t wtrimmed_length(const wchar_t* input, size_t input_len)
{
  while (input_len && input[input_len-1] == L' ')
  {--input_len;}

  return input_len;
}

/* Returns the trimmed length of the given string after
   discarding any trailing blanks. */
size_t trimmed_length(const char* input, size_t input_len)
{
  while (input_len && input[input_len-1] == ' ')
  {--input_len;}

  return input_len;
}


/* Defines properties for the buffer containing the JSON payload. */
typedef struct BufferInfo
{
  volatile wchar_t* baseptr;
  wchar_t* curptr;
  int   allocsize;
  volatile iconv_t  converter;
  int      truncated;
} BufferInfo_t;


/* Calculates the bytes allocated but unused for a given BufferInfo
    object.                                                        */
#define BYTES_REMAINING(buf) \
    (buf.allocsize - ((char*)buf.curptr-(char*)buf.baseptr))


/* Returns 1 if the buffer space can accommodate "size" bytes.
   Returns 0 and sets the truncated flag if otherwise.        */
inline int buffer_has_space(BufferInfo_t* buf, int size)
{
    if (!buf->truncated &&
        BYTES_REMAINING((*buf)) >= size)
      return 1;

    buf->truncated = 1;
    return 0;
}


/* JSON_KEY takes a null-terminated UTF-16 string, delimits
    it with quotes, adds a colon (:), and outputs it to the
    output buffer.                                          */
#define JSON_KEY(buf, key) \
    if (buffer_has_space(&buf, 6 + (sizeof(key)-2))) \
    { \
      wcscpy(buf.curptr, L"\""); ++buf.curptr; \
      wcscpy(buf.curptr,key); buf.curptr+=(sizeof(key)/2)-1; \
      wcscpy(buf.curptr, L"\":"); buf.curptr+=2; \
    }

/* JSON_STRING_VALUE takes a blank-padded string in the job CCSID,
    trims trailing blanks, converts the string to UTF-16, 
    delimits it with quotes, and copies it to the output buffer. */
#define JSON_STRING_VALUE(buf, value) \
    if (buffer_has_space(&buf, 2)) \
    { \
      wcscpy(buf.curptr, L"\""); ++buf.curptr; \
    } \
    if (!buf.truncated) \
    {\
      convert_to_utf16(value, \
                       trimmed_length(value, sizeof(value)), \
                       &buf.curptr,  \
                       BYTES_REMAINING(buf), \
                       buf.converter); \
    }\
    if (buffer_has_space(&buf,2)) \
    { \
      wcscpy(buf.curptr, L"\""); ++buf.curptr; \
    }

/* JSON_NUMERIC_VALUE takes a UTF-16 string of a given size
    and outputs it to the output buffer.                   */
#define JSON_NUMERIC_VALUE(buf, value, count) \
    if (buffer_has_space(&buf, count * 2)) \
    { \
      wcsncpy(buf.curptr, value, count); buf.curptr+=count; \
    }

/* JSON_ESCAPE_VALUE takes a UTF-16 string of a given size
    and outputs it to the output buffer after delimiting it
    and escaping any characters as defined by the JSON standard. */
#define JSON_ESCAPE_VALUE(buf, value, count) \
    if (buffer_has_space(&buf, 2)) \
    { \
      wcscpy(buf.curptr, L"\""); ++buf.curptr; \
    } \
    if (!buf.truncated) \
    {\
      json_escape(&buf.curptr, \
                  BYTES_REMAINING(buf) / 2, \
                  value, \
                  count); \
    }\
    if (buffer_has_space(&buf,2)) \
    { \
      wcscpy(buf.curptr, L"\""); ++buf.curptr; \
    }

/* JSON_APPEND takes a null-terminated UTF-16 string and
    outputs it to the output buffer.                       */
#define JSON_APPEND(buf, string) \
    if (buffer_has_space(&buf, sizeof(string)-2)) \
    { \
      wcscpy(buf.curptr, string); buf.curptr += (sizeof(string)/2)-1; \
    }

/* Cleanup handler function to ensure all resources are freed */
 void cleanup(_CNL_Hndlr_Parms_T* cancel_info)
 {
   Qp0zLprintf("SEND_JSON is cleaning up \n");
   BufferInfo_t* buf = (BufferInfo_t*)(cancel_info->Com_Area);
   free((void*)buf->baseptr);
   iconv_close(buf->converter);
 }
  	  	 
int main(int argc, char* argv[])
{
  const QQQ_QRYSV_QRYS0100_t* input = (QQQ_QRYSV_QRYS0100_t*)argv[1];
  int* rc = (int*)argv[2];

  BufferInfo_t buf;
  wchar_t translate_buffer[1024];
  time_t current_time;
  size_t char_count;
  wchar_t* work_ptr;
  size_t converted_bytes;
  struct tm *timeptr;
  char* inputAsCharData;
  char from_code[32], to_code[32];

  /* Don't terminate the query */
  *rc = 0;

  /* Set up the buffer used for the final JSON payload. */
  buf.baseptr = (wchar_t*)malloc(DataQMaxBytes);
  buf.allocsize = DataQMaxBytes;
  buf.curptr = (wchar_t*) buf.baseptr;
  buf.truncated = 0;

  /* Prepare the CCSID converter. We convert from Job CCCSID
      to CCSID 1200 (UTF-16).                                */
  memset(from_code, 0, sizeof(from_code));
  memset(to_code, 0, sizeof(to_code));
  memcpy(from_code, "IBMCCSID000000000000", 20);
  memcpy(to_code, "IBMCCSID01200", 13);
  buf.converter =  iconv_open(to_code, from_code);
  assert(buf.converter.return_value == 0);

#pragma cancel_handler (cleanup, buf)

  /* JSON encoding starts here. */
  JSON_APPEND(buf, L"{");

    /* Insert fixed length fields from the input.
       All fields must be converted into UTF-16 strings, except
        for the threshold name, which is already UTF-16. */

  JSON_KEY(buf, L"threshold_timestamp");
  JSON_APPEND(buf, L"\"");
  current_time = time(NULL);
  timeptr = localtime(&current_time);
  char_count = wcsftime(buf.curptr,
                        sizeof(translate_buffer)/2,
                        L"%F %T",
                        timeptr);
  buf.curptr += char_count;
  JSON_APPEND(buf, L"\"");

  JSON_APPEND(buf, L",");
  JSON_KEY(buf, L"job_name");
  JSON_STRING_VALUE(buf, input->Job_Name);

  JSON_APPEND(buf, L",");
  JSON_KEY(buf, L"job_user");
  JSON_STRING_VALUE(buf, input->Job_User);

  JSON_APPEND(buf, L",");
  JSON_KEY(buf, L"job_number");
  JSON_STRING_VALUE(buf, input->Job_Number);

  JSON_APPEND(buf, L",");
  JSON_KEY(buf, L"subsystem");
  JSON_STRING_VALUE(buf, input->Subsystem);

  JSON_APPEND(buf, L",");
  JSON_KEY(buf, L"user_name");
  JSON_STRING_VALUE(buf, input->User_Name);

  JSON_APPEND(buf, L",");
  JSON_KEY(buf, L"query_identifier");
  JSON_STRING_VALUE(buf, input->Query_Identifier_Long);

  JSON_APPEND(buf, L",");
  JSON_KEY(buf, L"query_plan_identifier");
  char_count = swprintf(translate_buffer,
                        sizeof(translate_buffer)/2,
                        L"%llu",
                        input->Plan_Identifier);
  JSON_NUMERIC_VALUE(buf, translate_buffer, char_count);

  JSON_APPEND(buf, L",");
  JSON_KEY(buf, L"threshold_name");
  JSON_ESCAPE_VALUE(buf,
                   (wchar_t*)(input->Threshold_Name),
                   wtrimmed_length((wchar_t*)(input->Threshold_Name),
                                   sizeof(input->Threshold_Name)/2));

  JSON_APPEND(buf, L",");
  JSON_KEY(buf, L"threshold_type");
  JSON_STRING_VALUE(buf, input->Threshold_Type);

  JSON_APPEND(buf, L",");
  JSON_KEY(buf, L"threshold_consumption_value");
  char_count = swprintf(translate_buffer,
                        sizeof(translate_buffer)/2,
                        L"%lld",
                        input->Threshold_Consumption_Value);
  JSON_NUMERIC_VALUE(buf, translate_buffer, char_count);

  JSON_APPEND(buf, L",");
  char_count = swprintf(translate_buffer,
                        sizeof(translate_buffer)/2,
                        L"%d",
                        (int)input->Operation_Type);
  JSON_KEY(buf, L"operation_type");
  JSON_NUMERIC_VALUE(buf, translate_buffer, char_count);


  /* Insert variable length fields.
     Client special registers need to be translated to UTF-16
      before they can be escaped and inserted.
     The SQL statement text and the host variable list can be
      copied over (with JSON escaping) as they are, since they
      are passed in as UTF-16 data.                           */

  inputAsCharData = (char*)input;

  if (input->Length_of_CLIENT_ACCTNG)
  {
    JSON_APPEND(buf, L",");
    JSON_KEY(buf, L"client_acctng");
    work_ptr = translate_buffer;
    converted_bytes =
      convert_to_utf16(inputAsCharData+input->Offset_to_CLIENT_ACCTNG,
                       input->Length_of_CLIENT_ACCTNG,
                       &work_ptr,
                       sizeof(translate_buffer),
                       buf.converter);
    JSON_ESCAPE_VALUE(buf, translate_buffer, converted_bytes / 2);
  }

  if (input->Length_of_CLIENT_APPLNAME)
  {
    JSON_APPEND(buf, L",");
    JSON_KEY(buf, L"client_applname");
    work_ptr = translate_buffer;
    converted_bytes =
      convert_to_utf16(inputAsCharData+input->Offset_to_CLIENT_APPLNAME,
                       input->Length_of_CLIENT_APPLNAME,
                       &work_ptr,
                       sizeof(translate_buffer),
                       buf.converter);
    JSON_ESCAPE_VALUE(buf, translate_buffer, converted_bytes / 2);
  }

  if (input->Length_of_CLIENT_PROGRAMID)
  {
    JSON_APPEND(buf, L",");
    JSON_KEY(buf, L"client_programid");
    work_ptr = translate_buffer;
    converted_bytes =
      convert_to_utf16(inputAsCharData+input->Offset_to_CLIENT_PROGRAMID,
                       input->Length_of_CLIENT_PROGRAMID,
                       &work_ptr,
                       sizeof(translate_buffer),
                       buf.converter);
    JSON_ESCAPE_VALUE(buf, translate_buffer, converted_bytes / 2);
  }

  if (input->Length_of_CLIENT_USERID)
  {
    JSON_APPEND(buf, L",");
    JSON_KEY(buf, L"client_userid");
    work_ptr = translate_buffer;
    converted_bytes =
      convert_to_utf16(inputAsCharData+input->Offset_to_CLIENT_USERID,
                       input->Length_of_CLIENT_USERID,
                       &work_ptr,
                       sizeof(translate_buffer),
                       buf.converter);
    JSON_ESCAPE_VALUE(buf, translate_buffer, converted_bytes / 2);
  }

  if (input->Length_of_CLIENT_WRKSTNNAME)
  {
    JSON_APPEND(buf, L",");
    JSON_KEY(buf, L"client_wrkstnname");
    work_ptr = translate_buffer;
    converted_bytes =
      convert_to_utf16(inputAsCharData+input->Offset_to_CLIENT_WRKSTNNAME,
                       input->Length_of_CLIENT_WRKSTNNAME,
                       &work_ptr,
                       sizeof(translate_buffer),
                       buf.converter);
    JSON_ESCAPE_VALUE(buf, translate_buffer, converted_bytes / 2);
  }

  if (input->Length_of_SQL_Statement)
  {
    JSON_APPEND(buf, L",");
    JSON_KEY(buf, L"sql_statement_text");
    JSON_ESCAPE_VALUE(buf,
      (wchar_t*)(inputAsCharData+input->Offset_to_SQL_Statement),
      input->Length_of_SQL_Statement/2);
  }

  if (input->Length_of_Host_Variable_List)
  {
    JSON_APPEND(buf, L",");
    JSON_KEY(buf, L"host_variable_list");
    JSON_ESCAPE_VALUE(buf,
      (wchar_t*)(inputAsCharData+input->Offset_to_Host_Variable_List),
      input->Length_of_Host_Variable_List/2);
  }

  JSON_APPEND(buf, L"}");


  if (buf.truncated)
  {
    Qp0zLprintf("SEND_JSON ran out of allocated space for "
                "constructing the Query Supervisor JSON data.\n");
  }

  QSNDDTAQ(DataQName,
           DataQLib,
           (decimal(5,0))((char*)buf.curptr-(char*)buf.baseptr),
           (void *)buf.baseptr
           );

#pragma disable_handler

  free((void*)buf.baseptr);
  iconv_close(buf.converter);

  return 0;
}