text:
/*
 * myth_profiler.c
 *
 *  Created on: 2012/10/02
 *      Author: denjo
 */

#include <sys/stat.h>
#include <stdarg.h>
#include "myth_profiler.h"
#include "myth_worker.h"
#include "myth_desc.h"
#include "myth_internal_lock.h"
#include "myth_init.h"
#include "myth_worker_func.h"
#include "assert.h"
#include "string.h"
#include "otf.h"

#define KEY_PROFILER_OFF 1
#define KEY_PROFILER_NUM_WORKERS 2
#define KEY_PROFILER_MEM_SIZE_LIMIT 3
#define KEY_PROFILER_DEPTH_LIMIT 4
#define KEY_PROFILER_NUM_PAPI_EVENTS 5
#define KEY_PROFILER_WATCH_FROM 6
#define KEY_PROFILER_WATCH_TO 7
#define KEY_PROFILER_WATCH_MODE 8
#define KEY_PROFILER_PAPI_EVENT_1 9
#define KEY_PROFILER_PAPI_EVENT_2 10
#define KEY_PROFILER_PAPI_EVENT_3 11
#define KEY_PROFILER_PAPI_EVENT_4 12
#define KEY_PROFILER_TASK_LEVEL 13
#define KEY_PROFILER_TASK_TREE_PATH 14
#define KEY_PROFILER_TIME_RECORD_TYPE 15

#define KEY_PROFILER_START_FUNCTION 16
#define KEY_PROFILER_START_SOURCE 17
#define KEY_PROFILER_START_TIME_RECORD_TYPE 18
#define KEY_PROFILER_STOP_FUNCTION 19
#define KEY_PROFILER_STOP_SOURCE 20
#define KEY_PROFILER_STOP_TIME_RECORD_TYPE 21

#define KEY_PROFILER_OTHER_TIME_RECORD_TYPES 22
#define KEY_PROFILER_START_OTHER_TIME_RECORD_TYPES 23
#define KEY_PROFILER_STOP_OTHER_TIME_RECORD_TYPES 24

#define KEY_PROFILER_FILE_NAME 25
#define KEY_PROFILER_LINE_NUMBER 26
#define KEY_PROFILER_FUNCTION 27
#define KEY_PROFILER_NUM_SUBTASKS 28
#define KEY_PROFILER_SPAWN_INDEX 29
#define KEY_PROFILER_SYNC_TASK_1 30
#define KEY_PROFILER_SYNC_TASK_2 31
#define KEY_PROFILER_SYNC_TASK_3 32
#define KEY_PROFILER_SYNC_TASK_4 33
#define KEY_PROFILER_SYNC_TASK_5 34

// For profiler
int profiler_num_workers = 0;	// number of workers

// Environment variables
char profiler_off = 0;				// To turn profiler off; on by default
char profiler_depth_limit = CHAR_MAX;	// Profiling depth limit, CHAR_MAX (127) means unlimited
char profiler_num_papi_events = 0;
int profiler_mem_size_limit;
char profiler_watch_from;
char profiler_watch_mode = 0;
char profiler_watch_to;
char * profiler_trace_name;
char profiler_lib_ins_off = 0;
char profiler_app_ins_off = 0;

// PAPI Events
int papi_event_codes[MAX_NUM_PAPI_EVENTS];
int default_papi_event_codes[MAX_NUM_PAPI_EVENTS] = {PAPI_L3_TCM, PAPI_L2_TCM, PAPI_L1_TCM, PAPI_L1_DCM};
char * papi_event_names[MAX_NUM_PAPI_EVENTS];
char * default_papi_event_names[MAX_NUM_PAPI_EVENTS] = {"PAPI_L3_TCM", "PAPI_L2_TCM", "PAPI_L1_TCM", "PAPI_L1_DCM"};

// PAPI
__thread int profiler_retval;

// Data filenames
__thread char * profiler_filename;

// OTF
OTF_FileManager * myManager;
OTF_Writer * myWriter;
__thread int profiler_otf_errno;
OTF_FileManager * profiler_otf_manager; // For application-level code instrumentation
OTF_Writer * profiler_otf_writer; // For application-level code instrumentation

// File for information
FILE * profiler_fp_overview;
myth_internal_lock_t * profiler_lock_fp_overview;

// Headers
void profiler_otf_init(OTF_FileManager **, OTF_Writer **, char * );
void profiler_otf_fini(OTF_FileManager **, OTF_Writer ** );
void profiler_libins_init();
void profiler_libins_fini();
void profiler_appins_init();
void profiler_appins_fini();
void profiler_write_to_file(int );
void profiler_libins_write_to_file(int );
void profiler_appins_write_to_file(int );
char profiler_appins_instrument(void * node, int line, int inscode, ...);

profiler_time_t profiler_get_curtime()
{
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return tv.tv_sec * 1.0E9 + tv.tv_usec * 1.0E3;
}

void profiler_PAPI_fail(char *file, int line, char *call, int retval)
{
    fprintf(stderr, "%s\tFAILED\nLine # %d\n", file, line);
    if ( retval == PAPI_ESYS ) {
        char buf[128];
        memset( buf, '\0', sizeof(buf) );
        sprintf(buf, "System error in %s:", call);
        perror(buf);
    }
    else if ( retval > 0 ) {
        fprintf(stderr, "Error calculating: %s\n", call );
    }
    else {
    	// PAPI 4.4.0
        //char errstring[PAPI_MAX_STR_LEN];
        //PAPI_perror(retval, errstring, PAPI_MAX_STR_LEN );
        //printf("Error in %s: %s\n", call, errstring );
    	// PAPI 5.0.1
    	fprintf(stderr, "Error in %s\nretval=%d\n", call, retval);
    	PAPI_perror(PAPI_strerror(retval));
    }
    fprintf(stderr, "\n");
    exit(1);
}

unsigned int profiler_RSHash(char* str, unsigned int len)
{
   unsigned int b    = 378551;
   unsigned int a    = 63689;
   unsigned int hash = 0;
   unsigned int i    = 0;

   for(i = 0; i < len; str++, i++)
   {
      hash = hash * a + (*str);
      a    = a * b;
   }

   return hash;
}

void profiler_otf_check(char * file, int line, int errno, int expected) {
	if (errno != expected) {
		printf("OTF Error at %s:%d:\n%s\n", file, line, otf_strerr);
		assert(profiler_otf_errno);
	}
}

int profiler_check_memory_usage() {
#ifdef PROFILER_ON
	// Get environment
	myth_running_env_t env;
	env = myth_get_current_env();

	unsigned int usage = env->num_libins_nodes * sizeof(profiler_libins_task_node) +
	  env->num_libins_records * sizeof(profiler_libins_time_record) +
	  env->num_appins_nodes * sizeof(profiler_appins_task_node) +
	  env->num_appins_records * sizeof(profiler_appins_time_record);
	if (usage < profiler_mem_size_limit << 20)
		return 1;
	else
		return 0;
#else
	return 1;
#endif /*PROFILER_ON*/
}
profiler_libins_task_node_t profiler_malloc_libins_task_node(int worker) {
	/* PROFILER_OFF */
	if (profiler_off) return NULL;

	profiler_libins_task_node_t ret = NULL;
	ret = myth_flmalloc(worker, sizeof(profiler_libins_task_node));
	//assert(ret != NULL);

	return ret;
}

profiler_libins_time_record_t profiler_malloc_libins_time_record(int worker) {
	/* PROFILER_OFF */
	if (profiler_off) return NULL;

	profiler_libins_time_record_t ret = NULL;
	ret = myth_flmalloc(worker, sizeof(profiler_libins_time_record));
	//assert(ret != NULL);

	return ret;
}

profiler_appins_task_node_t profiler_malloc_appins_task_node(int worker) {
	/* PROFILER_OFF */
	if (profiler_off) return NULL;

	profiler_appins_task_node_t ret = NULL;
	ret = myth_flmalloc(worker, sizeof(profiler_appins_task_node));
	//assert(ret != NULL);

	return ret;
}

profiler_appins_time_record_t profiler_malloc_appins_time_record(int worker) {
	/* PROFILER_OFF */
	if (profiler_off) return NULL;

	profiler_appins_time_record_t ret = NULL;
	ret = myth_flmalloc(worker, sizeof(profiler_appins_time_record));
	//assert(ret != NULL);

	return ret;
}

void profiler_free_libins_task_node(int worker, profiler_libins_task_node_t node) {
#ifdef PROFILER_ON
	assert(node);
	if (node->level != 0)
		myth_flfree(worker, (strlen(node->tree_path)+1) * sizeof(char), node->tree_path);
	myth_flfree(worker, sizeof(profiler_libins_task_node), node);
#endif /*PROFILER_ON*/
}

void profiler_free_libins_time_record(int worker, profiler_libins_time_record_t record) {
#ifdef PROFILER_ON
	assert(record);
	myth_flfree(worker, sizeof(profiler_appins_time_record), record);
#endif /*PROFILER_ON*/
}

void profiler_free_appins_task_node(int worker, profiler_appins_task_node_t node) {
#ifdef PROFILER_ON
	assert(node);
	if (node->tree_path)
	  myth_flfree(worker, (strlen(node->tree_path)+1) * sizeof(char), node->tree_path);
	myth_flfree(worker, sizeof(profiler_appins_task_node), node);
#endif /*PROFILER_ON*/
}

void profiler_free_appins_time_record(int worker, profiler_appins_time_record_t record) {
#ifdef PROFILER_ON
	assert(record);
	myth_flfree(worker, sizeof(profiler_appins_time_record), record);
#endif /*PROFILER_ON*/
}

profiler_libins_time_record_t profiler_create_time_record(int type, int worker, profiler_time_t time) {
#ifdef PROFILER_ON
	/* PROFILER_OFF */
	if (profiler_off) return NULL;

	// Allocate memory
	profiler_libins_time_record_t record;
	record = profiler_malloc_libins_time_record(worker);

	// Set up fields
	record->type = type;
	record->task_index = 0;
	record->time = time;
	record->scl = 0;
	int i;
	for (i=0; i<profiler_num_papi_events; i++) {
		record->values[i] = 0;
	}
	record->node = NULL;
	record->next = NULL;

	// Return
	return record;
#else
	return NULL;
#endif /*PROFILER_ON*/
}

void profiler_init(int worker_thread_num) {
#ifdef PROFILER_ON
	// Get enviroment variable
	char * env_var;

	// PROFILER_OFF environment variable
	env_var = getenv(ENV_PROFILER_OFF);
	if (env_var)
		profiler_off = atoi(env_var);
	if (profiler_off) return;

	// PROFILER_DEPTH_LIMIT environment variable
	env_var = getenv(ENV_PROFILER_DEPTH_LIMIT);
	if (env_var) {
		profiler_depth_limit = atoi(env_var);
	}

	// PROFILER_NUM_PAPI_EVENTS environment variable
	env_var = getenv(ENV_PROFILER_NUM_PAPI_EVENTS);
	if (env_var) {
		profiler_num_papi_events = atoi(env_var);
		if (profiler_num_papi_events > MAX_NUM_PAPI_EVENTS)
			profiler_num_papi_events = MAX_NUM_PAPI_EVENTS;
	}

	// PROFILER_MEM_SIZE_LIMIT environment variable
	env_var = getenv(ENV_PROFILER_MEM_SIZE_LIMIT);
	if (env_var) {
		profiler_mem_size_limit = atoi(env_var);
	} else {
		profiler_mem_size_limit = EACH_CORE_MEMORY_SIZE_LIMIT;
	}

	// PROFILER_WATCH_FROM environment variable
	env_var = getenv(ENV_PROFILER_WATCH_FROM);
	if (env_var) {
		profiler_watch_from = atoi(env_var);
	} else {
		profiler_watch_from = profiler_depth_limit + 1;
		if (profiler_watch_from < 0)
		  profiler_watch_from = 127;
	}

	// PROFILER_WATCH_MODE environment variable
	env_var = getenv(ENV_PROFILER_WATCH_MODE);
	if (env_var) {
		profiler_watch_mode = atoi(env_var);
	}

	// PROFILER_WATCH_TO environment variable
	env_var = getenv(ENV_PROFILER_WATCH_TO);
	if (env_var) {
		profiler_watch_to = atoi(env_var);
	} else {
		profiler_watch_to = 127;
	}

	// PROFILER_TRACE_NAME environment variable
	env_var = getenv(ENV_PROFILER_TRACE_NAME);
	if (env_var) {
		profiler_trace_name = env_var;
	} else {
		profiler_trace_name = "anyname";
	}

	// PROFILER_LIB_INS_OFF environment variable
	env_var = getenv(ENV_PROFILER_LIB_INSTRUMENT_OFF);
	if (env_var)
		profiler_lib_ins_off = atoi(env_var);

	// PROFILER_OFF environment variable
	env_var = getenv(ENV_PROFILER_APP_INSTRUMENT_OFF);
	if (env_var)
		profiler_app_ins_off = atoi(env_var);

	// General initialization
	profiler_num_workers = worker_thread_num;

	int i;

	// Make prof folder
	mkdir(DIR_FOR_PROF_DATA, S_IRWXU | S_IRWXG | S_IROTH);
	// Open overview data file
	char * trace_file_name = malloc( ( strlen(profiler_trace_name) + 20 ) * sizeof(char) );
	sprintf(trace_file_name, "tsprof/%s_summary.txt", profiler_trace_name);
	profiler_fp_overview = fopen(trace_file_name, "w");
	profiler_lock_fp_overview = (myth_internal_lock_t *) malloc(sizeof(myth_internal_lock_t));
	myth_internal_lock_init(profiler_lock_fp_overview);

	// PAPI
	// Initialize PAPI library
	if (profiler_num_papi_events > 0) {
		profiler_retval = PAPI_library_init(PAPI_VER_CURRENT);
		if (profiler_retval != PAPI_VER_CURRENT)
			profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_library_init", profiler_retval);
		// Initialize PAPI's thread function
		profiler_retval = PAPI_thread_init( (unsigned long (*) (void)) real_pthread_self);
		if (profiler_retval != PAPI_OK)
			profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_thread_init", profiler_retval);
	}

	// PROFILER_PAPI_EVENT_x environment variables
	char name[MAX_PAPI_EVENT_NAME_LENGTH];
	for (i=0; i<profiler_num_papi_events; i++) {
		sprintf(name, "%s%d", ENV_PROFILER_PAPI_EVENT_NAME, i+1);
		papi_event_names[i] = getenv(name);
		if (papi_event_names[i] == NULL) {
			papi_event_names[i] = default_papi_event_names[i];
			papi_event_codes[i] = default_papi_event_codes[i];
		} else {
			if ((profiler_retval = PAPI_event_name_to_code(papi_event_names[i], &papi_event_codes[i])) != PAPI_OK)
				profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_event_name_to_code", profiler_retval);
		}
	}

	// OTF: initialize OTF traces
	profiler_libins_init();
	profiler_appins_init();

#endif /*PROFILER_ON*/
}

void profiler_init_worker(int worker) {
#ifdef PROFILER_ON
	/* PROFILER_OFF */
	if (profiler_off) return;

	// OTF: write event BeginProcess
	profiler_time_t time = profiler_get_curtime();
	if (myWriter) {
		profiler_otf_errno = OTF_Writer_writeBeginProcess(myWriter, time, worker + 1);
		assert(profiler_otf_errno);
	}
	if (profiler_otf_writer) {
		profiler_otf_errno = OTF_Writer_writeBeginProcess(profiler_otf_writer, time, worker + 1);
		assert(profiler_otf_errno);
	}

	// Ant: [PAPI] register this worker thread
	if (worker != 0 && profiler_num_papi_events > 0) {
		profiler_retval = PAPI_register_thread();
		if (profiler_retval != PAPI_OK)
			profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_register_thread", profiler_retval);
	}

	// Initialize worker thread-local variables
	g_envs[worker].EventSet = PAPI_NULL;
	if (profiler_num_papi_events > 0)
		g_envs[worker].values = (long long *) malloc(profiler_num_papi_events * sizeof(long long));
	g_envs[worker].num_libins_nodes = 0;
	g_envs[worker].num_libins_records = 0;
	g_envs[worker].head_lr = NULL;
	g_envs[worker].tail_lr = NULL;
	g_envs[worker].num_appins_nodes = 0;
	g_envs[worker].num_appins_records = 0;
	g_envs[worker].head_ar = NULL;
	g_envs[worker].tail_ar = NULL;

	int i;

	// Initialize EventSet
	if (profiler_num_papi_events > 0) {
		if ((profiler_retval = PAPI_create_eventset(&g_envs[worker].EventSet)) != PAPI_OK)
			profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_create_eventset", profiler_retval);
		for (i=0; i<profiler_num_papi_events; i++) {
			if ((profiler_retval = PAPI_add_event(g_envs[worker].EventSet, papi_event_codes[i])) != PAPI_OK)
				profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_add_event", profiler_retval);
		}
		if ((profiler_retval = PAPI_start(g_envs[worker].EventSet)) != PAPI_OK)
			profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_start", profiler_retval);
	}
#endif /*PROFILER_ON*/
}

void profiler_fini_worker(int worker) {
#ifdef PROFILER_ON
	/* PROFILER_OFF */
	if (profiler_off) return;

	// Write data to file
	profiler_write_to_file(worker);

	// Worker thread-local variables
	if (profiler_filename != NULL)
		free(profiler_filename);

	// Stop PAPI
	if (profiler_num_papi_events > 0) {
		if ((profiler_retval = PAPI_stop(g_envs[worker].EventSet, g_envs[worker].values)) != PAPI_OK)
			profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_stop", profiler_retval);
		if (worker != 0) {
			profiler_retval = PAPI_unregister_thread();
			if (profiler_retval != PAPI_OK)
				profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_unregister_thread", profiler_retval);
		}
	}

	// OTF: write event EndProcess
	if (myWriter) {
		profiler_otf_errno = OTF_Writer_writeEndProcess(myWriter, profiler_get_curtime(), worker + 1);
		assert(profiler_otf_errno);
	}
	if (profiler_otf_writer) {
		profiler_otf_errno = OTF_Writer_writeEndProcess(profiler_otf_writer, profiler_get_curtime(), worker + 1);
		assert(profiler_otf_errno);
	}
#endif /*PROFILER_ON*/
}

void profiler_fini() {
#ifdef PROFILER_ON
	/* PROFILER_OFF */
	if (profiler_off) return;

	// Destroy overview file lock
	myth_internal_lock_destroy(profiler_lock_fp_overview);
	//free(profiler_lock_fp_overview);

	// Close overview file
	fclose(profiler_fp_overview);

	// OTF: terminate OTF traces
	profiler_libins_fini();
	profiler_appins_fini();

#endif /*PROFILER_ON*/
}

void profiler_write_to_file(int worker) {
#ifdef PROFILER_ON
	/* PROFILER_OFF */
	if (profiler_off) return;

	profiler_libins_write_to_file(worker);
	profiler_appins_write_to_file(worker);

#endif /*PROFILER_ON*/
}

profiler_libins_task_node_t profiler_create_root_node() {
#ifdef PROFILER_ON
#ifdef PROFILER_LIB_INSTRUMENT_ON
	/* PROFILER OFF */
	if (profiler_off) return NULL;
	/* PROFILER_LIB_INS_OFF */
	if (profiler_lib_ins_off) return NULL;

	/* Get environment */
	myth_running_env_t env;
	env = myth_get_current_env();

	/* Get current worker thread */
	int worker = env->rank;

	/* Allocate memory */
	profiler_libins_task_node_t new_node;
	new_node = profiler_malloc_libins_task_node(worker);

	/* Increment env's libins node counter */
	env->num_libins_nodes++;

	/* Set fields */
	new_node->tree_path = "0";
	new_node->index = profiler_RSHash(new_node->tree_path, strlen(new_node->tree_path));
	new_node->level = 0;
	new_node->function_name = "function_name"; // not implemented yet
	new_node->head_scl = 0; // not implemented yet
	new_node->tail_scl = 0; // not implemented yet
	new_node->subtask_count = 0;
	new_node->record_count = 0;
	new_node->ended = 0;

	/* Return */
	return new_node;
#else
	return NULL;
#endif /*PROFILER_LIB_INSTRUMENT_ON*/
#else
	return NULL;
#endif /*PROFILER_ON*/
}

profiler_libins_task_node_t profiler_create_new_node(profiler_libins_task_node_t parent) {
#ifdef PROFILER_ON
#ifdef PROFILER_LIB_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return NULL;
	/* PROFILER_LIB_INS_OFF */
	if (profiler_lib_ins_off) return NULL;

	/* Check profiling limit */
	if (parent == NULL || parent->level == profiler_depth_limit)
		return NULL;

	/* Get environment */
	myth_running_env_t env;
	env = myth_get_current_env();

	/* Get current worker thread */
	int worker = env->rank;

	/* Allocate memory */
	profiler_libins_task_node_t new_node;
	new_node = profiler_malloc_libins_task_node(worker);

	/* Increment env's libins node counter */
	env->num_libins_nodes++;

	/* Set fields */
	int length = strlen(parent->tree_path) + 5;
	new_node->tree_path = (char *) myth_flmalloc(worker, length * sizeof(char));
	sprintf(new_node->tree_path, "%s_%d", parent->tree_path, parent->subtask_count);
	new_node->index = profiler_RSHash(new_node->tree_path, strlen(new_node->tree_path));
	new_node->level = parent->level + 1;
	new_node->function_name = "function_name"; // not implemented yet
	new_node->head_scl = 0; // not implemented yet
	new_node->tail_scl = 0; // not implemented yet
	new_node->subtask_count = 0;
	new_node->record_count = 0;
	new_node->ended = 0;
	parent->subtask_count++;

	/* Return */
	return new_node;
#else
	return NULL;
#endif /*PROFILER_LIB_INSTRUMENT_ON*/
#else
	return NULL;
#endif /*PROFILER_ON*/
}

void profiler_add_time_start(void * thread_t, int worker, int start_code) {
#ifdef PROFILER_ON
#ifdef PROFILER_LIB_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return;
	/* PROFILER_LIB_INS_OFF */
	if (profiler_lib_ins_off) return;

	/* Get task node */
	assert(thread_t);
	myth_thread_t thread = (myth_thread_t) thread_t;
	profiler_libins_task_node_t node = thread->node;

	/* Check profiling limit */
	if (node == NULL) return;

	/* Get environment */
	myth_running_env_t env;
	env = myth_get_current_env();

	/* Allocate memory */
	profiler_libins_time_record_t record = NULL;
	record = profiler_create_time_record(start_code << 1, worker, 0);

	/* Attach to env's time record list */
	if (env->num_libins_records == 0) {
		env->head_lr = record;
		env->tail_lr = record;
	} else {
		//assert(env->tail != NULL);
		env->tail_lr->next = record;
		env->tail_lr = record;
	}
	env->num_libins_records++;

	/* Set remained fields */
	record->task_index = node->index;
	record->node = node;

	/* Increment node's counter */
	node->record_count++;

	/* Get time, counter values */
	if (profiler_num_papi_events > 0) {
		if ((profiler_retval = PAPI_read(g_envs[worker].EventSet, g_envs[worker].values)) != PAPI_OK)
			profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_read", profiler_retval);
	}
	profiler_time_t time = profiler_get_curtime();

	/* Assign time, counter values */
	record->time = time;
	int i;
	for (i=0; i<profiler_num_papi_events; i++)
		record->values[i] = g_envs[worker].values[i];

	/* Measurement data limitation (must be after record data assignment) */
	if (profiler_check_memory_usage() == 0)
		profiler_write_to_file(worker);
#endif /*PROFILER_LIB_INSTRUMENT_ON*/
#endif /*PROFILER_ON*/
}

void profiler_add_time_stop(void * thread_t, int worker, int stop_code) {
#ifdef PROFILER_ON
#ifdef PROFILER_LIB_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return;
	/* PROFILER_LIB_INS_OFF */
	if (profiler_lib_ins_off) return;

	/* Get task node */
	assert(thread_t);
	myth_thread_t thread = (myth_thread_t) thread_t;
	profiler_libins_task_node_t node = thread->node;

	/* Check profiling limit */
	if (node == NULL) return;

	/* Get environment */
	myth_running_env_t env;
	env = myth_get_current_env();

	/* Get time, counter values */
	profiler_time_t time = profiler_get_curtime();
	if (profiler_num_papi_events > 0) {
		if ((profiler_retval = PAPI_read(g_envs[worker].EventSet, g_envs[worker].values)) != PAPI_OK)
			profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_read", profiler_retval);
	}

	/* Allocate memory */
	profiler_libins_time_record_t record = NULL;
	record = profiler_create_time_record((stop_code << 1) + 1, worker, time);

	/* Attach to env's time record list */
	if (env->num_libins_records == 0) {
		env->head_lr = record;
		env->tail_lr = record;
	} else {
		//assert(env->tail != NULL);
		env->tail_lr->next = record;
		env->tail_lr = record;
	}
	env->num_libins_records++;

	/* Set remained fields */
	record->task_index = node->index;
	record->node = node;

	/* Increment node's counter */
	node->record_count++;

	/* Task's end*/
	if (stop_code == 13)
		node->ended = 1;

	/* Assign counter values */
	int i;
	for (i=0; i<profiler_num_papi_events; i++)
		record->values[i] = g_envs[worker].values[i];

	/* Measurement data limitation (must be after record data assignment) */
	if (profiler_check_memory_usage() == 0)
		profiler_write_to_file(worker);
#endif /*PROFILER_LIB_INSTRUMENT_ON*/
#endif /*PROFILER_ON*/
}

void profiler_libins_init() {
#ifdef PROFILER_ON
#ifdef PROFILER_LIB_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return;
	/* PROFILER_LIB_INS_OFF */
	if (profiler_lib_ins_off) return;

	char * trace_file_name = malloc( ( strlen(profiler_trace_name) + 15 ) * sizeof(char) );
	sprintf(trace_file_name, "tsprof/%s_lib", profiler_trace_name);
	profiler_otf_init(&myManager, &myWriter, trace_file_name);

#endif /*PROFILER_LIB_INSTRUMENT_ON*/
#endif /*PROFILER_ON*/
}

void profiler_libins_fini() {
#ifdef PROFILER_ON
#ifdef PROFILER_LIB_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return;
	/* PROFILER_LIB_INS_OFF */
	if (profiler_lib_ins_off) return;

	profiler_otf_fini(&myManager, &myWriter);

#endif /*PROFILER_LIB_INSTRUMENT_ON*/
#endif /*PROFILER_ON*/
}

void profiler_libins_write_to_file(int worker) {
#ifdef PROFILER_ON
#ifdef PROFILER_LIB_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return;
	/* PROFILER_LIB_INS_OFF */
	if (profiler_lib_ins_off) return;

	/* Get start time */
	profiler_time_t start_time = profiler_get_curtime();

	/* Get environment */
	myth_running_env_t env;
	env = myth_get_current_env();

	/* Write libins_records */
	profiler_libins_time_record_t record = env->head_lr;
	profiler_libins_time_record_t record_t = NULL;
	int num_records_written = 0;
	int num_records = env->num_libins_records;
	int num_nodes_deleted = 0;
	int num_nodes = env->num_libins_nodes;
	while (record != NULL) {

		// Key-value list
		OTF_KeyValueList * kvlist;
		kvlist = OTF_KeyValueList_new();
		assert(kvlist);

		// Get numbers of starts and stops
		int i;
		int num_starts = 0;
		int num_stops = 0;
		profiler_libins_time_record_t start_record = NULL;
		profiler_libins_time_record_t stop_record = NULL;
		record_t = record;
		while (record_t != NULL && record_t->type % 2 == 0) {
			num_starts++;
			start_record = record_t;
			record_t = record_t->next;
		}
		stop_record = record_t;
		while (record_t != NULL && record_t->type % 2 == 1) {
			num_stops++;
			record_t = record_t->next;
		}

		if (num_starts == 0) {

			fprintf(stderr, "Error: num_starts == 0, num_stops == %d (type=%d)\n", num_stops, record->type);
			// Add a stop time record without any associated start time record
			profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_STOP_FUNCTION, record->task_index);
			assert(profiler_otf_errno == 0);
			profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_STOP_SOURCE, record->scl);
			assert(profiler_otf_errno == 0);
			profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_STOP_TIME_RECORD_TYPE, record->type);
			assert(profiler_otf_errno == 0);
			for (i=0; i<profiler_num_papi_events; i++) {
				profiler_otf_errno = OTF_KeyValueList_appendUint64(kvlist, KEY_PROFILER_PAPI_EVENT_1 + i, record->values[i]);
				assert(profiler_otf_errno == 0);
			}
			profiler_otf_errno = OTF_Writer_writeEventCommentKV(myWriter, record->time, worker+1, "stop time record without any associated start time record", kvlist);
			assert(profiler_otf_errno);
			num_records_written++;

		} else {

			if (num_stops == 0) {

				fprintf(stderr, "Error: num_stops == 0, num_starts == %d\n", num_starts);
				// Add a start time record without any associated stop time record
				profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_START_FUNCTION, start_record->task_index);
				assert(profiler_otf_errno == 0);
				profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_START_SOURCE, start_record->scl);
				assert(profiler_otf_errno == 0);
				profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_START_TIME_RECORD_TYPE, start_record->type);
				assert(profiler_otf_errno == 0);
				for (i=0; i<profiler_num_papi_events; i++) {
					profiler_otf_errno = OTF_KeyValueList_appendUint64(kvlist, KEY_PROFILER_PAPI_EVENT_1 + i, start_record->values[i]);
					assert(profiler_otf_errno == 0);
				}
				profiler_otf_errno = OTF_Writer_writeEventCommentKV(myWriter, start_record->time, worker+1, "start time record without any associated stop time record", kvlist);
				assert(profiler_otf_errno);
				num_records_written++;

			} else {

				unsigned char * start_types_str = NULL;
				unsigned char * stop_types_str = NULL;
				if (num_starts > 1) {
					start_types_str = malloc(num_starts * sizeof(char));
					int i;
					profiler_libins_time_record_t record_temp = record;
					for (i=0; i<num_starts; i++, record_temp=record_temp->next)
						start_types_str[i] = record_temp->type;
				}
				if (num_stops > 1) {
					stop_types_str = malloc(num_stops * sizeof(char));
					int i;
					profiler_libins_time_record_t record_temp = record;
					for (i=0; i<num_stops; i++, record_temp=record_temp->next)
						stop_types_str[i] = record_temp->type;
				}

				if (start_record->time == stop_record->time)
					fprintf(stderr, "Error: start_record->time == stop_record->time, num_starts=%d, num_stops=%d\n", num_starts, num_stops);
/*				if (start_record->time == stop_record->time) {
					fprintf(stderr, "Error: start_record->time == stop_record->time, num_starts=%d, num_stops=%d\n", num_starts, num_stops);
					// Aggregate start and stop records
					profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_START_FUNCTION, start_record->task_index);
					assert(profiler_otf_errno == 0);
					profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_START_SOURCE, start_record->scl);
					assert(profiler_otf_errno == 0);
					profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_START_TIME_RECORD_TYPE, start_record->type);
					assert(profiler_otf_errno == 0);
					profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_STOP_FUNCTION, stop_record->task_index);
					assert(profiler_otf_errno == 0);
					profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_STOP_SOURCE, stop_record->scl);
					assert(profiler_otf_errno == 0);
					profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_STOP_TIME_RECORD_TYPE, stop_record->type);
					assert(profiler_otf_errno == 0);
					if (start_types_str) {
						profiler_otf_errno = OTF_KeyValueList_appendByteArray(kvlist, KEY_PROFILER_START_OTHER_TIME_RECORD_TYPES, (unsigned char *) start_types_str, num_starts);
						assert(profiler_otf_errno == 0);
					}
					if (stop_types_str) {
						profiler_otf_errno = OTF_KeyValueList_appendByteArray(kvlist, KEY_PROFILER_STOP_OTHER_TIME_RECORD_TYPES, (unsigned char *) stop_types_str, num_stops);
						assert(profiler_otf_errno == 0);
					}
					profiler_otf_errno = OTF_Writer_writeEventCommentKV(myWriter, start_record->time, worker+1, "aggregate start and stop time record", kvlist);
					assert(profiler_otf_errno);
					num_records_written++;
				} else {*/
					// Write each start and stop
					OTF_KeyValueList * kvlist2;
					kvlist2 = OTF_KeyValueList_new();
					assert(kvlist2);

					// kvlist for start record
					profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_TIME_RECORD_TYPE, start_record->type);
					assert(profiler_otf_errno == 0);
					if (start_types_str) {
						profiler_otf_errno = OTF_KeyValueList_appendByteArray(kvlist, KEY_PROFILER_START_OTHER_TIME_RECORD_TYPES, (unsigned char *) start_types_str, num_starts);
						assert(profiler_otf_errno == 0);
					}
					for (i=0; i<profiler_num_papi_events; i++) {
						profiler_otf_errno = OTF_KeyValueList_appendUint64(kvlist, KEY_PROFILER_PAPI_EVENT_1 + i, start_record->values[i]);
						assert(profiler_otf_errno == 0);
					}
					// kvlist2 for stop record
					profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist2, KEY_PROFILER_TIME_RECORD_TYPE, stop_record->type);
					assert(profiler_otf_errno == 0);
					if (stop_types_str) {
						profiler_otf_errno = OTF_KeyValueList_appendByteArray(kvlist, KEY_PROFILER_STOP_OTHER_TIME_RECORD_TYPES, (unsigned char *) stop_types_str, num_stops);
						assert(profiler_otf_errno == 0);
					}
					for (i=0; i<profiler_num_papi_events; i++) {
						profiler_otf_errno = OTF_KeyValueList_appendUint64(kvlist2, KEY_PROFILER_PAPI_EVENT_1 + i, stop_record->values[i]);
						assert(profiler_otf_errno == 0);
					}

					// Write Enter and Leave records
					profiler_otf_errno = OTF_Writer_writeEnterKV(myWriter, start_record->time, start_record->task_index, worker+1, start_record->scl, kvlist);
					if (profiler_otf_errno == 0) {
						printf("%d: Errorrrrrrrrrrrrrrrrrrrr\n%s\n%llu\n", worker, otf_strerr, start_record->time);
						assert(profiler_otf_errno);
					}
					profiler_otf_errno = OTF_Writer_writeLeaveKV(myWriter, stop_record->time, stop_record->task_index, worker+1, stop_record->scl, kvlist2);
					if (profiler_otf_errno == 0) {
						printf("%d: Errorrrrr\n%s\n%llu\n", worker, otf_strerr, stop_record->time);
						assert(profiler_otf_errno);
					}
					num_records_written += 2;

				//}
			}

		}

		// Free memory
		profiler_libins_time_record_t record_temp;
		while (record != record_t) {
			record_temp = record->next;
			profiler_libins_task_node_t node = record->node;
			node->record_count--;
			if (node->record_count == 0 && node->ended == 1) {

				// Write libins_node
				OTF_KeyValueList * kvlist;
				kvlist = OTF_KeyValueList_new();
				assert(kvlist);
				profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_TASK_LEVEL, node->level);
				assert(profiler_otf_errno == 0);
				profiler_otf_errno = OTF_KeyValueList_appendByteArray(kvlist, KEY_PROFILER_TASK_TREE_PATH, (unsigned char *) node->tree_path, strlen(node->tree_path));
				assert(profiler_otf_errno == 0);
				// Write task_node
				profiler_otf_errno = OTF_Writer_writeDefFunctionKV(myWriter, worker+1, node->index, node->function_name, 0, node->head_scl, kvlist);
				assert(profiler_otf_errno);

				// Delete libins_node
				g_envs[node->worker].num_appins_nodes--;
				profiler_free_libins_task_node(node->worker, node);
				num_nodes_deleted++;

			}
			profiler_free_libins_time_record(worker, record);
			record = record_temp;
		}
	}
	env->head_lr = NULL;
	env->tail_lr = NULL;
	env->num_libins_records = 0;

	// Get stop time
	profiler_time_t stop_time = profiler_get_curtime();

	// Output period info
	myth_internal_lock_lock(profiler_lock_fp_overview);
	fprintf(profiler_fp_overview, "\nprofiler_libins_write_to_file()\n");
	fprintf(profiler_fp_overview, "worker: %d\n", worker);
	fprintf(profiler_fp_overview, "number of libins records (written/total): %d/%d\n", num_records_written, num_records);
	fprintf(profiler_fp_overview, "number of deleted libins nodes (on various workers): %d\n", num_nodes_deleted);
	fprintf(profiler_fp_overview, "number of libins nodes (of this worker): %d\n", num_nodes);
	fprintf(profiler_fp_overview, "from: %llu\n", start_time);
	fprintf(profiler_fp_overview, "to  : %llu\n", stop_time);
	fprintf(profiler_fp_overview, "Time cost: %llu\n", stop_time-start_time);
	myth_internal_lock_unlock(profiler_lock_fp_overview);

#endif /*PROFILER_LIB_INSTRUMENT_ON*/
#endif /*PROFILER_ON*/
}

void profiler_otf_init(OTF_FileManager **manager, OTF_Writer **writer, char * trace_name) {
	// OTF: initialize
	*manager = OTF_FileManager_open(100);
	assert(*manager);
	*writer = OTF_Writer_open(trace_name, profiler_num_workers, *manager);
	assert(*writer);
	profiler_otf_errno = OTF_Writer_writeDefTimerResolution(*writer, 0, 1000000);
	assert(profiler_otf_errno);

	int i;

	// OTF: register DefKeyValue
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_OFF, OTF_INT32, "profiler_off", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_NUM_WORKERS, OTF_INT32, "profiler_num_workers", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_MEM_SIZE_LIMIT, OTF_INT32, "profiler_mem_size_limit", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_DEPTH_LIMIT, OTF_INT32, "profiler_depth_limit", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_NUM_PAPI_EVENTS, OTF_INT32, "profiler_num_papi_events", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_WATCH_FROM, OTF_INT32, "profiler_watch_from", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_WATCH_TO, OTF_INT32, "profiler_watch_to", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_WATCH_MODE, OTF_INT32, "profiler_watch_mode", NULL);
	assert(profiler_otf_errno);
	for (i=0; i<profiler_num_papi_events; i++) {
		profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_PAPI_EVENT_1+i, OTF_UINT64, "papi_event_i", papi_event_names[i]);
		assert(profiler_otf_errno);
	}
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_TASK_LEVEL, OTF_INT32, "task level", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_TASK_TREE_PATH, OTF_BYTE_ARRAY, "task tree path", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_TIME_RECORD_TYPE, OTF_INT32, "time record type", NULL);
	assert(profiler_otf_errno);

	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_START_FUNCTION, OTF_INT32, "start function", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_START_SOURCE, OTF_INT32, "start source", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_START_TIME_RECORD_TYPE, OTF_INT32, "start time record type", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_STOP_FUNCTION, OTF_INT32, "stop function", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_STOP_SOURCE, OTF_INT32, "stop source", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_STOP_TIME_RECORD_TYPE, OTF_INT32, "stop time record type", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_OTHER_TIME_RECORD_TYPES, OTF_BYTE_ARRAY, "other time record types", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_START_OTHER_TIME_RECORD_TYPES, OTF_BYTE_ARRAY, "other start time record types", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_STOP_OTHER_TIME_RECORD_TYPES, OTF_BYTE_ARRAY, "other stop time record types", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_FILE_NAME, OTF_BYTE_ARRAY, "file name", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_LINE_NUMBER, OTF_INT32, "line number", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_FUNCTION, OTF_BYTE_ARRAY, "function", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_NUM_SUBTASKS, OTF_INT32, "number of subtasks", NULL);
	assert(profiler_otf_errno);
	profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_SPAWN_INDEX, OTF_INT32, "spawn index", NULL);
	assert(profiler_otf_errno);
	for (i=0; i<MAX_LENGTH_SUBTASK_LIST; i++) {
		profiler_otf_errno = OTF_Writer_writeDefKeyValue(*writer, 0, KEY_PROFILER_SYNC_TASK_1+i, OTF_INT32, "sync_task_i", NULL);
		assert(profiler_otf_errno);
	}

	// OTF: write KeyValue
	OTF_KeyValueList * kvlist;
	kvlist = OTF_KeyValueList_new();
	assert(kvlist);
	profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_OFF, (int) profiler_off);
	assert(profiler_otf_errno == 0);
	profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_NUM_WORKERS, (int) profiler_num_workers);
	assert(profiler_otf_errno == 0);
	profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_MEM_SIZE_LIMIT, (int) profiler_mem_size_limit);
	assert(profiler_otf_errno == 0);
	profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_DEPTH_LIMIT, (int) profiler_depth_limit);
	assert(profiler_otf_errno == 0);
	profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_NUM_PAPI_EVENTS, (int) profiler_num_papi_events);
	assert(profiler_otf_errno == 0);
	profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_WATCH_FROM, (int) profiler_watch_from);
	assert(profiler_otf_errno == 0);
	profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_WATCH_TO, (int) profiler_watch_to);
	assert(profiler_otf_errno == 0);
	profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_WATCH_MODE, (int) profiler_watch_mode);
	assert(profiler_otf_errno == 0);
	for (i=0; i<profiler_num_papi_events; i++) {
		profiler_otf_errno = OTF_KeyValueList_appendUint64(kvlist, KEY_PROFILER_PAPI_EVENT_1+i, 1);
		assert(profiler_otf_errno == 0);
	}

	// OTF: write a NoOpKV event
	profiler_otf_errno = OTF_Writer_writeDefinitionCommentKV(*writer, 0, "Overview information", kvlist);
	assert(profiler_otf_errno);

	// OTF: create MasterControl entries for all processes
	OTF_MasterControl * mc = OTF_Writer_getMasterControl(*writer);
	for (i=0; i<profiler_num_workers; i++) {
		OTF_MasterControl_append(mc, i+1, i+1);
	}
	// OTF: force creating all streams
	for (i=0; i<profiler_num_workers; i++) {
		OTF_Writer_getStream(*writer, i+1);
	}
	// OTF: register processes
	for (i=0; i<profiler_num_workers; i++) {
		char * worker_name = (char *) malloc(20 * sizeof(char));
		sprintf(worker_name, "Worker thread %d", i);
		profiler_otf_errno = OTF_Writer_writeDefProcess(*writer, i+1, i+1, worker_name, 0);
		assert(profiler_otf_errno);
	}
}

void profiler_otf_fini(OTF_FileManager **manager, OTF_Writer **writer) {
	// OTF: finalize
	OTF_Writer_close(*writer);
	OTF_FileManager_close(*manager);
}

void profiler_appins_init() {
#ifdef PROFILER_ON
#ifdef PROFILER_APP_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return;
	/* PROFILER_APP_INS_OFF */
	if (profiler_app_ins_off) return;

	char * trace_file_name = malloc( ( strlen(profiler_trace_name) + 15 ) * sizeof(char) );
	sprintf(trace_file_name, "tsprof/%s_app", profiler_trace_name);
	profiler_otf_init(&profiler_otf_manager, &profiler_otf_writer, trace_file_name);

#endif /*PROFILER_APP_INSTRUMENT_ON*/
#endif /*PROFILER_ON*/
}

void profiler_appins_fini() {
#ifdef PROFILER_ON
#ifdef PROFILER_APP_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return;
	/* PROFILER_APP_INS_OFF */
	if (profiler_app_ins_off) return;

	profiler_otf_fini(&profiler_otf_manager, &profiler_otf_writer);

#endif /*PROFILER_APP_INSTRUMENT_ON*/
#endif /*PROFILER_ON*/
}

void profiler_appins_write_to_file(int worker) {
#ifdef PROFILER_ON
#ifdef PROFILER_APP_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return;
	/* PROFILER_APP_INS_OFF */
	if (profiler_app_ins_off) return;

	/* Get start time */
	profiler_time_t start_time = profiler_get_curtime();

	/* Get environment */
	myth_running_env_t env;
	env = myth_get_current_env();

	/* Write appins_records */
	profiler_appins_time_record_t record = env->head_ar;
	profiler_appins_time_record_t record_t;
	int num_records_written = 0;
	int num_records = env->num_appins_records;
	int num_nodes_deleted = 0;
	int num_nodes = env->num_appins_nodes;
	while (record != NULL) {
		// Get element
		record_t = record->next;
		// Create key-value list for appins_time_record
		OTF_KeyValueList * kvlist;
		kvlist = OTF_KeyValueList_new();
		assert(kvlist);

		// Append level, tree_path, file, line, counters
		profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_TASK_LEVEL, record->node->level);
		assert(profiler_otf_errno == 0);
		profiler_otf_errno = OTF_KeyValueList_appendByteArray(kvlist, KEY_PROFILER_TASK_TREE_PATH, (unsigned char *) record->node->tree_path, strlen(record->node->tree_path));
		assert(profiler_otf_errno == 0);
		profiler_otf_errno = OTF_KeyValueList_appendByteArray(kvlist, KEY_PROFILER_FILE_NAME, (unsigned char *) record->node->file, strlen(record->node->file));
		assert(profiler_otf_errno == 0);
		profiler_otf_errno = OTF_KeyValueList_appendByteArray(kvlist, KEY_PROFILER_FUNCTION, (unsigned char *) record->node->function, strlen(record->node->file));
		assert(profiler_otf_errno == 0);
		profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_LINE_NUMBER, record->line);
		assert(profiler_otf_errno == 0);
		int i;
		for (i=0; i<profiler_num_papi_events; i++) {
			profiler_otf_errno = OTF_KeyValueList_appendUint64(kvlist, KEY_PROFILER_PAPI_EVENT_1 + i, record->values[i]);
			assert(profiler_otf_errno == 0);
		}
		if (record->inscode == PROFILER_APPINS_SPAWN) {
			profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_SPAWN_INDEX, record->subtask_list[0]);
			assert(profiler_otf_errno == 0);
		}
		if (record->inscode == PROFILER_APPINS_SYNC) {
			int num = record->subtask_list[0];
			for (i=0; i<num; i++) {
				profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_SYNC_TASK_1 + i, record->subtask_list[i+1]);
				assert(profiler_otf_errno == 0);
			}
		}
		if (record->inscode == PROFILER_APPINS_END) {
			profiler_otf_errno = OTF_KeyValueList_appendInt32(kvlist, KEY_PROFILER_NUM_SUBTASKS, record->node->subtask_count);
			assert(profiler_otf_errno == 0);
		}
		// Write appins_time_record
		unsigned int counter = (profiler_num_papi_events > 0)?record->values[0]:0;
		profiler_otf_errno = OTF_Writer_writeCounterKV(profiler_otf_writer, record->time, worker+1, record->inscode, counter, kvlist);
		//assert(profiler_otf_errno);
		if (profiler_otf_errno != 1) {
			printf("OTF Error at %s:%d:\n%sInscode=%d\nWorker=%d\n\n", __FILE__, __LINE__, otf_strerr, record->inscode, worker);
			//assert(profiler_otf_errno);
		}

		// Adjust task_node
		record->node->record_count--;
		if (record->node->record_count == 0 && record->node->ended) {
			g_envs[record->node->worker].num_appins_nodes--;
			profiler_free_appins_task_node(record->node->worker, record->node);
			num_nodes_deleted++;
		}

		// Free time_record
		profiler_free_appins_time_record(worker, record);

		// Increment counter
		num_records_written++;
		record = record_t;
	}
	env->head_ar = NULL;
	env->tail_ar = NULL;
	env->num_appins_records = 0;
	//fprintf(stderr, "profiler_function_write(): %d out of %d\n", num_records_written, num_records);

	// Get stop time
	profiler_time_t stop_time = profiler_get_curtime();

	// Output period info
	myth_internal_lock_lock(profiler_lock_fp_overview);
	fprintf(profiler_fp_overview, "\nprofiler_appins_write_to_file()\n");
	fprintf(profiler_fp_overview, "worker: %d\n", worker);
	fprintf(profiler_fp_overview, "number of appins records (written/total): %d/%d\n", num_records_written, num_records);
	fprintf(profiler_fp_overview, "number of deleted appins nodes (on various workers): %d\n", num_nodes_deleted);
	fprintf(profiler_fp_overview, "number of appins nodes (of this worker): %d\n", num_nodes);
	fprintf(profiler_fp_overview, "from: %llu\n", start_time);
	fprintf(profiler_fp_overview, "to  : %llu\n", stop_time);
	fprintf(profiler_fp_overview, "Time cost: %llu\n", stop_time-start_time);
	myth_internal_lock_unlock(profiler_lock_fp_overview);

#endif /*PROFILER_APP_INSTRUMENT_ON*/
#endif /*PROFILER_ON*/
}

char * profiler_appins_create_tree_path(int worker, char * tree_path, int next_value) {
#ifdef PROFILER_ON
#ifdef PROFILER_APP_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return NULL;
	/* PROFILER_APP_INS_OFF */
	if (profiler_app_ins_off) return NULL;

	int length = strlen(tree_path) + 5;
	char * new_tree_path = (char *) myth_flmalloc(worker, length * sizeof(char));
	sprintf(new_tree_path, "%s_%d", tree_path, next_value);
	return new_tree_path;

#else
	return NULL;
#endif /*PROFILER_APP_INSTRUMENT_ON*/
#else
	return NULL;
#endif /*PROFILER_ON*/
}

void * profiler_appins_begin(const char *file, const char *function, void *parent_node_void, char spawn_index, int line) {
#ifdef PROFILER_ON
#ifdef PROFILER_APP_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return NULL;
	/* PROFILER_APP_INS_OFF */
	if (profiler_app_ins_off) return NULL;

	/* Get environment */
	myth_running_env_t env;
	env = myth_get_current_env();

	/* Get current worker thread */
	int worker = env->rank;

	/* Get parent level and tree path */
	profiler_appins_task_node_t parent_node = (profiler_appins_task_node_t) parent_node_void;
	int level = (parent_node==NULL)?1:(parent_node->level+1);
	char * parent_tree_path = (parent_node==NULL)?"0":(parent_node->tree_path);

	/* Allocate memory */
	profiler_appins_task_node_t new_node;
	new_node = profiler_malloc_appins_task_node(worker);

	/* Increment env's appins node counter */
	env->num_appins_nodes++;

	/* Set fields */
	new_node->file = (char *) myth_flmalloc(worker, (strlen(file)+1) * sizeof(char) );
	strcpy(new_node->file, file);
	new_node->function = (char *) myth_flmalloc(worker, (strlen(function)+1) * sizeof(char) );
	strcpy(new_node->function, function);
	new_node->level = level;
	new_node->tree_path = profiler_appins_create_tree_path(worker, parent_tree_path, spawn_index);
	new_node->worker = worker;
	new_node->subtask_count = 0;
	new_node->record_count = 0;
	new_node->ended = 0;

	/* Call profiler_appins_instrument() for PROFILER_APPINS_BEGIN */
	profiler_appins_instrument((void *) new_node, line, PROFILER_APPINS_BEGIN);

	/* Return */
	return (void *) new_node;

#else
	return NULL;
#endif /* PROFILER_APP_INSTRUMENT_ON */
#else
	return NULL;
#endif /* PROFILER_ON */
}

char profiler_appins_instrument(void * node_void, int line, int inscode, ...) {
#ifdef PROFILER_ON
#ifdef PROFILER_APP_INSTRUMENT_ON
	/* PROFILER_OFF */
	if (profiler_off) return -1;
	/* PROFILER_APP_INS_OFF */
	if (profiler_app_ins_off) return -1;

	/* Return value */
	char retval = -1;

	/* Get environment */
	myth_running_env_t env;
	env = myth_get_current_env();

	/* Get current worker thread */
	int worker = env->rank;

	/* Get time, counter values */
	profiler_time_t time = 0;
	switch (inscode) {
	case PROFILER_APPINS_SPAWN:
	case PROFILER_APPINS_PAUSE:
	case PROFILER_APPINS_END:
	  // Time
	  time = profiler_get_curtime();
	  // Counter values
	  if (profiler_num_papi_events > 0)
		if ((profiler_retval = PAPI_read(g_envs[worker].EventSet, g_envs[worker].values)) != PAPI_OK)
		  profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_read", profiler_retval);
	  // Break
	  break;
	}

	/* Get task_node */
	profiler_appins_task_node_t node = (profiler_appins_task_node_t) node_void;

	/* Measurement data limitation (must be after record data assignment) */
	if (profiler_check_memory_usage() == 0)
		profiler_write_to_file(worker);

	/* Allocate memory */
	profiler_appins_time_record_t new_record;
	new_record = profiler_malloc_appins_time_record(worker);

	/* Attach to env's time record list */
	if (env->num_appins_records == 0) {
		env->head_ar = new_record;
		env->tail_ar = new_record;
	} else {
		//assert(env->tail != NULL);
		env->tail_ar->next = new_record;
		env->tail_ar = new_record;
	}
	env->num_appins_records++;

	/* Set up fields */
	new_record->line = line;
	new_record->inscode = inscode;
	new_record->node = node;
	new_record->next = NULL;

	/* Increment node's counter*/
	node->record_count++;

	/* Particular processes basing on inscode*/
	switch (inscode) {
	  va_list vargs;
	case PROFILER_APPINS_SPAWN:
	  retval = node->subtask_count++;
	  new_record->subtask_list[0] = retval;
	  break;
	case PROFILER_APPINS_SYNC:
	  va_start(vargs, inscode);
	  int num = va_arg(vargs, int);
	  if (num > MAX_LENGTH_SUBTASK_LIST) {
		fprintf(stderr, "Error: num=%d > MAX_LENGTH_SUBTASK_LIST=%d\n", num, MAX_LENGTH_SUBTASK_LIST);
		num = MAX_LENGTH_SUBTASK_LIST;
	  }
	  int i;
	  char id;
	  new_record->subtask_list[0] = num;
	  for (i=1; i<num+1; i++) {
		id = (char) va_arg(vargs, int);
		new_record->subtask_list[i] = id;
	  }
	  va_end(vargs);
	  break;
	case PROFILER_APPINS_END:
	  node->ended = 1;
	  break;
	}

	/* Get time, counter values */
	switch (inscode) {
	case PROFILER_APPINS_BEGIN:
	case PROFILER_APPINS_RESUME:
	case PROFILER_APPINS_SYNC:
	  // Counter values
	  if (profiler_num_papi_events > 0)
		if ((profiler_retval = PAPI_read(g_envs[worker].EventSet, g_envs[worker].values)) != PAPI_OK)
		  profiler_PAPI_fail(__FILE__, __LINE__, "PAPI_read", profiler_retval);
	  // Time
	  time = profiler_get_curtime();
	  // Break
	  break;
	}

	/* Assign time, counter values */
	new_record->time = time;
	int i;
	for (i=0; i<profiler_num_papi_events; i++)
	  new_record->values[i] = g_envs[worker].values[i];

	/* Return */
	return retval;

#endif /* PROFILER_APP_INSTRUMENT_ON */
#endif /* PROFILER_ON */
}


text:
/**
 * @name SDL.AppController
 * @desc App general logic
 * @category Controller
 * @filesource app/controller/AppController.js
 * @version 1.0
 */

SDL.AppController = Em.Object.create( {
    active: false
});

redpajama_set_name: RedPajamaGithub


text:
package org.elasticsearch.xpack.sql.analysis.analyzer;

import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.TestUtils;
import org.elasticsearch.xpack.sql.analysis.index.EsIndex;
import org.elasticsearch.xpack.sql.analysis.index.IndexResolution;
import org.elasticsearch.xpack.sql.analysis.index.IndexResolverTests;
import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.Greatest;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.IfNull;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.Least;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIf;
import org.elasticsearch.xpack.sql.parser.SqlParser;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.stats.Metrics;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.EsField;
import org.elasticsearch.xpack.sql.type.TypesTests;

import java.util.LinkedHashMap;
import java.util.Map;

import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;

public class VerifierErrorMessagesTests extends ESTestCase {

    private SqlParser parser = new SqlParser();
    private IndexResolution indexResolution = IndexResolution.valid(new EsIndex("test",
        TypesTests.loadMapping("mapping-multi-field-with-nested.json")));

    private String error(String sql) {
        return error(indexResolution, sql);
    }

    private String error(IndexResolution getIndexResult, String sql) {
        Analyzer analyzer = new Analyzer(TestUtils.TEST_CFG, new FunctionRegistry(), getIndexResult, new Verifier(new Metrics()));
        VerificationException e = expectThrows(VerificationException.class, () -> analyzer.analyze(parser.createStatement(sql), true));
        assertTrue(e.getMessage().startsWith("Found "));
        String header = "Found 1 problem(s)\nline ";
        return e.getMessage().substring(header.length());
    }

    private LogicalPlan accept(String sql) {
        EsIndex test = getTestEsIndex();
        return accept(IndexResolution.valid(test), sql);
    }

    private EsIndex getTestEsIndex() {
        Map<String, EsField> mapping = TypesTests.loadMapping("mapping-multi-field-with-nested.json");
        return new EsIndex("test", mapping);
    }

    private LogicalPlan accept(IndexResolution resolution, String sql) {
        Analyzer analyzer = new Analyzer(TestUtils.TEST_CFG, new FunctionRegistry(), resolution, new Verifier(new Metrics()));
        return analyzer.analyze(parser.createStatement(sql), true);
    }

    private IndexResolution incompatible() {
        Map<String, EsField> basicMapping = TypesTests.loadMapping("mapping-basic.json", true);
        Map<String, EsField> incompatible = TypesTests.loadMapping("mapping-basic-incompatible.json");

        assertNotEquals(basicMapping, incompatible);
        IndexResolution resolution = IndexResolverTests.merge(new EsIndex("basic", basicMapping),
                new EsIndex("incompatible", incompatible));
        assertTrue(resolution.isValid());
        return resolution;
    }

    private String incompatibleError(String sql) {
        return error(incompatible(), sql);
    }

    private LogicalPlan incompatibleAccept(String sql) {
        return accept(incompatible(), sql);
    }

    public void testMissingIndex() {
        assertEquals("1:17: Unknown index [missing]", error(IndexResolution.notFound("missing"), "SELECT foo FROM missing"));
    }

    public void testMissingColumn() {
        assertEquals("1:8: Unknown column [xxx]", error("SELECT xxx FROM test"));
    }

    public void testMissingColumnFilter() {
        assertEquals("1:26: Unknown column [xxx]", error("SELECT * FROM test WHERE xxx > 1"));
    }

    public void testMissingColumnWithWildcard() {
        assertEquals("1:8: Unknown column [xxx]", error("SELECT xxx.* FROM test"));
    }

    public void testMisspelledColumnWithWildcard() {
        assertEquals("1:8: Unknown column [tex], did you mean [text]?", error("SELECT tex.* FROM test"));
    }

    public void testColumnWithNoSubFields() {
        assertEquals("1:8: Cannot determine columns for [text.*]", error("SELECT text.* FROM test"));
    }

    public void testFieldAliasTypeWithoutHierarchy() {
        Map<String, EsField> mapping = new LinkedHashMap<>();

        mapping.put("field", new EsField("field", DataType.OBJECT,
                singletonMap("alias", new EsField("alias", DataType.KEYWORD, emptyMap(), true)), false));

        IndexResolution resolution = IndexResolution.valid(new EsIndex("test", mapping));

        // check the nested alias is seen
        accept(resolution, "SELECT field.alias FROM test");
        // or its hierarhcy
        accept(resolution, "SELECT field.* FROM test");

        // check typos
        assertEquals("1:8: Unknown column [field.alas], did you mean [field.alias]?", error(resolution, "SELECT field.alas FROM test"));

        // non-existing parents for aliases are not seen by the user
        assertEquals("1:8: Cannot use field [field] type [object] only its subfields", error(resolution, "SELECT field FROM test"));
    }

    public void testMultipleColumnsWithWildcard1() {
        assertEquals("1:14: Unknown column [a]\n" +
                "line 1:17: Unknown column [b]\n" +
                "line 1:22: Unknown column [c]\n" +
                "line 1:25: Unknown column [tex], did you mean [text]?", error("SELECT bool, a, b.*, c, tex.* FROM test"));
    }

    public void testMultipleColumnsWithWildcard2() {
        assertEquals("1:8: Unknown column [tex], did you mean [text]?\n" +
                "line 1:21: Unknown column [a]\n" +
                "line 1:24: Unknown column [dat], did you mean [date]?\n" +
                "line 1:31: Unknown column [c]", error("SELECT tex.*, bool, a, dat.*, c FROM test"));
    }

    public void testMultipleColumnsWithWildcard3() {
        assertEquals("1:8: Unknown column [ate], did you mean [date]?\n" +
                "line 1:21: Unknown column [keyw], did you mean [keyword]?\n" +
                "line 1:29: Unknown column [da], did you mean [date]?" , error("SELECT ate.*, bool, keyw.*, da FROM test"));
    }

    public void testMisspelledColumn() {
        assertEquals("1:8: Unknown column [txt], did you mean [text]?", error("SELECT txt FROM test"));
    }

    public void testFunctionOverMissingField() {
        assertEquals("1:12: Unknown column [xxx]", error("SELECT ABS(xxx) FROM test"));
    }

    public void testFunctionOverMissingFieldInFilter() {
        assertEquals("1:30: Unknown column [xxx]", error("SELECT * FROM test WHERE ABS(xxx) > 1"));
    }

    public void testMissingFunction() {
        assertEquals("1:8: Unknown function [ZAZ]", error("SELECT ZAZ(bool) FROM test"));
    }

    public void testMisspelledFunction() {
        assertEquals("1:8: Unknown function [COONT], did you mean any of [COUNT, COT, CONCAT]?", error("SELECT COONT(bool) FROM test"));
    }

    public void testMissingColumnInGroupBy() {
        assertEquals("1:41: Unknown column [xxx]", error("SELECT * FROM test GROUP BY DAY_OF_YEAR(xxx)"));
    }

    public void testInvalidOrdinalInOrderBy() {
        assertEquals("1:56: Invalid ordinal [3] specified in [ORDER BY 2, 3] (valid range is [1, 2])",
                error("SELECT bool, MIN(int) FROM test GROUP BY 1 ORDER BY 2, 3"));
    }

    public void testFilterOnUnknownColumn() {
        assertEquals("1:26: Unknown column [xxx]", error("SELECT * FROM test WHERE xxx = 1"));
    }

    public void testMissingColumnInOrderBy() {
        assertEquals("1:29: Unknown column [xxx]", error("SELECT * FROM test ORDER BY xxx"));
    }

    public void testMissingColumnFunctionInOrderBy() {
        assertEquals("1:41: Unknown column [xxx]", error("SELECT * FROM test ORDER BY DAY_oF_YEAR(xxx)"));
    }

    public void testMissingExtract() {
        assertEquals("1:8: Unknown datetime field [ZAZ]", error("SELECT EXTRACT(ZAZ FROM date) FROM test"));
    }

    public void testMissingExtractSimilar() {
        assertEquals("1:8: Unknown datetime field [DAP], did you mean [DAY]?", error("SELECT EXTRACT(DAP FROM date) FROM test"));
    }

    public void testMissingExtractSimilarMany() {
        assertEquals("1:8: Unknown datetime field [DOP], did you mean any of [DOM, DOW, DOY, IDOW]?",
            error("SELECT EXTRACT(DOP FROM date) FROM test"));
    }

    public void testExtractNonDateTime() {
        assertEquals("1:8: Invalid datetime field [ABS]. Use any datetime function.", error("SELECT EXTRACT(ABS FROM date) FROM test"));
    }

    public void testDateTruncInvalidArgs() {
        assertEquals("1:8: first argument of [DATE_TRUNC(int, date)] must be [string], found value [int] type [integer]",
            error("SELECT DATE_TRUNC(int, date) FROM test"));
        assertEquals("1:8: second argument of [DATE_TRUNC(keyword, keyword)] must be [date or datetime], found value [keyword] " +
                "type [keyword]", error("SELECT DATE_TRUNC(keyword, keyword) FROM test"));
        assertEquals("1:8: first argument of [DATE_TRUNC('invalid', keyword)] must be one of [MILLENNIUM, CENTURY, DECADE, " + "" +
                "YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND] " +
                "or their aliases; found value ['invalid']",
            error("SELECT DATE_TRUNC('invalid', keyword) FROM test"));
        assertEquals("1:8: Unknown value ['millenioum'] for first argument of [DATE_TRUNC('millenioum', keyword)]; " +
                "did you mean [millennium, millennia]?",
            error("SELECT DATE_TRUNC('millenioum', keyword) FROM test"));
        assertEquals("1:8: Unknown value ['yyyz'] for first argument of [DATE_TRUNC('yyyz', keyword)]; " +
                "did you mean [yyyy, yy]?",
            error("SELECT DATE_TRUNC('yyyz', keyword) FROM test"));
    }

    public void testDateTruncValidArgs() {
        accept("SELECT DATE_TRUNC('decade', date) FROM test");
        accept("SELECT DATE_TRUNC('decades', date) FROM test");
        accept("SELECT DATE_TRUNC('day', date) FROM test");
        accept("SELECT DATE_TRUNC('days', date) FROM test");
        accept("SELECT DATE_TRUNC('dd', date) FROM test");
        accept("SELECT DATE_TRUNC('d', date) FROM test");

    }

    public void testDatePartInvalidArgs() {
        assertEquals("1:8: first argument of [DATE_PART(int, date)] must be [string], found value [int] type [integer]",
            error("SELECT DATE_PART(int, date) FROM test"));
        assertEquals("1:8: second argument of [DATE_PART(keyword, keyword)] must be [date or datetime], found value [keyword] " +
            "type [keyword]", error("SELECT DATE_PART(keyword, keyword) FROM test"));
        assertEquals("1:8: first argument of [DATE_PART('invalid', keyword)] must be one of [YEAR, QUARTER, MONTH, DAYOFYEAR, " +
            "DAY, WEEK, WEEKDAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND, TZOFFSET] " +
                "or their aliases; found value ['invalid']",
            error("SELECT DATE_PART('invalid', keyword) FROM test"));
        assertEquals("1:8: Unknown value ['tzofset'] for first argument of [DATE_PART('tzofset', keyword)]; " +
                "did you mean [tzoffset]?",
            error("SELECT DATE_PART('tzofset', keyword) FROM test"));
        assertEquals("1:8: Unknown value ['dz'] for first argument of [DATE_PART('dz', keyword)]; " +
                "did you mean [dd, tz, dw, dy, d]?",
            error("SELECT DATE_PART('dz', keyword) FROM test"));
    }

    public void testDatePartValidArgs() {
        accept("SELECT DATE_PART('weekday', date) FROM test");
        accept("SELECT DATE_PART('dw', date) FROM test");
        accept("SELECT DATE_PART('tz', date) FROM test");
        accept("SELECT DATE_PART('dayofyear', date) FROM test");
        accept("SELECT DATE_PART('dy', date) FROM test");
        accept("SELECT DATE_PART('ms', date) FROM test");
    }

    public void testDateAddInvalidArgs() {
        assertEquals("1:8: first argument of [DATE_ADD(int, int, date)] must be [string], found value [int] type [integer]",
            error("SELECT DATE_ADD(int, int, date) FROM test"));
        assertEquals("1:8: second argument of [DATE_ADD(keyword, keyword, date)] must be [integer], found value [keyword] " +
            "type [keyword]", error("SELECT DATE_ADD(keyword, keyword, date) FROM test"));
        assertEquals("1:8: third argument of [DATE_ADD(keyword, int, keyword)] must be [date or datetime], found value [keyword] " +
            "type [keyword]", error("SELECT DATE_ADD(keyword, int, keyword) FROM test"));
        assertEquals("1:8: first argument of [DATE_ADD('invalid', int, date)] must be one of [YEAR, QUARTER, MONTH, DAYOFYEAR, " +
                "DAY, WEEK, WEEKDAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND] " +
                "or their aliases; found value ['invalid']",
            error("SELECT DATE_ADD('invalid', int, date) FROM test"));
        assertEquals("1:8: Unknown value ['sacinds'] for first argument of [DATE_ADD('sacinds', int, date)]; " +
                "did you mean [seconds, second]?",
            error("SELECT DATE_ADD('sacinds', int, date) FROM test"));
        assertEquals("1:8: Unknown value ['dz'] for first argument of [DATE_ADD('dz', int, date)]; " +
                "did you mean [dd, dw, dy, d]?",
            error("SELECT DATE_ADD('dz', int, date) FROM test"));
    }

    public void testDateAddValidArgs() {
        accept("SELECT DATE_ADD('weekday', 0, date) FROM test");
        accept("SELECT DATE_ADD('dw', 20, date) FROM test");
        accept("SELECT DATE_ADD('years', -10, date) FROM test");
        accept("SELECT DATE_ADD('dayofyear', 123, date) FROM test");
        accept("SELECT DATE_ADD('dy', 30, date) FROM test");
        accept("SELECT DATE_ADD('ms', 1, date::date) FROM test");
    }

    public void testValidDateTimeFunctionsOnTime() {
        accept("SELECT HOUR_OF_DAY(CAST(date AS TIME)) FROM test");
        accept("SELECT MINUTE_OF_HOUR(CAST(date AS TIME)) FROM test");
        accept("SELECT MINUTE_OF_DAY(CAST(date AS TIME)) FROM test");
        accept("SELECT SECOND_OF_MINUTE(CAST(date AS TIME)) FROM test");
    }

    public void testInvalidDateTimeFunctionsOnTime() {
        assertEquals("1:8: argument of [DAY_OF_YEAR(CAST(date AS TIME))] must be [date or datetime], " +
                "found value [CAST(date AS TIME)] type [time]",
            error("SELECT DAY_OF_YEAR(CAST(date AS TIME)) FROM test"));
    }

    public void testGroupByOnTimeNotAllowed() {
        assertEquals("1:36: Function [CAST(date AS TIME)] with data type [time] cannot be used for grouping",
            error("SELECT count(*) FROM test GROUP BY CAST(date AS TIME)"));
    }

    public void testGroupByOnTimeWrappedWithScalar() {
        accept("SELECT count(*) FROM test GROUP BY MINUTE(CAST(date AS TIME))");
    }

    public void testHistogramOnTimeNotAllowed() {
        assertEquals("1:8: first argument of [HISTOGRAM] must be [date, datetime or numeric], " +
                "found value [CAST(date AS TIME)] type [time]",
            error("SELECT HISTOGRAM(CAST(date AS TIME), INTERVAL 1 MONTH), COUNT(*) FROM test GROUP BY 1"));
    }

    public void testSubtractFromInterval() {
        assertEquals("1:8: Cannot subtract a datetime[CAST('2000-01-01' AS DATETIME)] " +
                "from an interval[INTERVAL 1 MONTH]; do you mean the reverse?",
            error("SELECT INTERVAL 1 MONTH - CAST('2000-01-01' AS DATETIME)"));

        assertEquals("1:8: Cannot subtract a time[CAST('12:23:56.789' AS TIME)] " +
                "from an interval[INTERVAL 1 MONTH]; do you mean the reverse?",
            error("SELECT INTERVAL 1 MONTH - CAST('12:23:56.789' AS TIME)"));
    }

    public void testAddIntervalAndNumberNotAllowed() {
        assertEquals("1:8: [+] has arguments with incompatible types [INTERVAL_DAY] and [INTEGER]",
            error("SELECT INTERVAL 1 DAY + 100"));
        assertEquals("1:8: [+] has arguments with incompatible types [INTEGER] and [INTERVAL_DAY]",
            error("SELECT 100 + INTERVAL 1 DAY"));
    }

    public void testSubtractIntervalAndNumberNotAllowed() {
        assertEquals("1:8: [-] has arguments with incompatible types [INTERVAL_MINUTE] and [DOUBLE]",
            error("SELECT INTERVAL 10 MINUTE - 100.0"));
        assertEquals("1:8: [-] has arguments with incompatible types [DOUBLE] and [INTERVAL_MINUTE]",
            error("SELECT 100.0 - INTERVAL 10 MINUTE"));
    }

    public void testMultiplyIntervalWithDecimalNotAllowed() {
        assertEquals("1:8: [*] has arguments with incompatible types [INTERVAL_MONTH] and [DOUBLE]",
            error("SELECT INTERVAL 1 MONTH * 1.234"));
        assertEquals("1:8: [*] has arguments with incompatible types [DOUBLE] and [INTERVAL_MONTH]",
            error("SELECT 1.234 * INTERVAL 1 MONTH"));
    }

    public void testMultipleColumns() {
        assertEquals("1:43: Unknown column [xxx]\nline 1:8: Unknown column [xxx]",
                error("SELECT xxx FROM test GROUP BY DAY_oF_YEAR(xxx)"));
    }

    // GROUP BY
    public void testGroupBySelectWithAlias() {
        assertNotNull(accept("SELECT int AS i FROM test GROUP BY i"));
    }

    public void testGroupBySelectWithAliasOrderOnActualField() {
        assertNotNull(accept("SELECT int AS i FROM test GROUP BY i ORDER BY int"));
    }

    public void testGroupBySelectNonGrouped() {
        assertEquals("1:8: Cannot use non-grouped column [text], expected [int]",
                error("SELECT text, int FROM test GROUP BY int"));
    }

    public void testGroupByFunctionSelectFieldFromGroupByFunction() {
        assertEquals("1:8: Cannot use non-grouped column [int], expected [ABS(int)]",
                error("SELECT int FROM test GROUP BY ABS(int)"));
    }

    public void testGroupByOrderByNonGrouped() {
        assertEquals("1:50: Cannot order by non-grouped column [bool], expected [text]",
                error("SELECT MAX(int) FROM test GROUP BY text ORDER BY bool"));
    }

    public void testGroupByOrderByNonGrouped_WithHaving() {
        assertEquals("1:71: Cannot order by non-grouped column [bool], expected [text]",
            error("SELECT MAX(int) FROM test GROUP BY text HAVING MAX(int) > 10 ORDER BY bool"));
    }

    public void testGroupByOrdinalPointingToAggregate() {
        assertEquals("1:42: Ordinal [2] in [GROUP BY 2] refers to an invalid argument, aggregate function [MIN(int)]",
                error("SELECT bool, MIN(int) FROM test GROUP BY 2"));
    }

    public void testGroupByInvalidOrdinal() {
        assertEquals("1:42: Invalid ordinal [3] specified in [GROUP BY 3] (valid range is [1, 2])",
                error("SELECT bool, MIN(int) FROM test GROUP BY 3"));
    }

    public void testGroupByNegativeOrdinal() {
        assertEquals("1:42: Invalid ordinal [-1] specified in [GROUP BY -1] (valid range is [1, 2])",
                error("SELECT bool, MIN(int) FROM test GROUP BY -1"));
    }

    public void testGroupByOrderByAliasedInSelectAllowed() {
        LogicalPlan lp = accept("SELECT int i FROM test GROUP BY int ORDER BY i");
        assertNotNull(lp);
    }

    public void testGroupByOrderByScalarOverNonGrouped() {
        assertEquals("1:50: Cannot order by non-grouped column [YEAR(date)], expected [text] or an aggregate function",
                error("SELECT MAX(int) FROM test GROUP BY text ORDER BY YEAR(date)"));
    }

    public void testGroupByOrderByFieldFromGroupByFunction() {
        assertEquals("1:54: Cannot use non-grouped column [int], expected [ABS(int)]",
                error("SELECT ABS(int) FROM test GROUP BY ABS(int) ORDER BY int"));
    }

    public void testGroupByOrderByScalarOverNonGrouped_WithHaving() {
        assertEquals("1:71: Cannot order by non-grouped column [YEAR(date)], expected [text] or an aggregate function",
            error("SELECT MAX(int) FROM test GROUP BY text HAVING MAX(int) > 10 ORDER BY YEAR(date)"));
    }

    public void testGroupByHavingNonGrouped() {
        assertEquals("1:48: Cannot use HAVING filter on non-aggregate [int]; use WHERE instead",
                error("SELECT AVG(int) FROM test GROUP BY text HAVING int > 10"));
    }

    public void testGroupByAggregate() {
        assertEquals("1:36: Cannot use an aggregate [AVG] for grouping",
                error("SELECT AVG(int) FROM test GROUP BY AVG(int)"));
    }

    public void testStarOnNested() {
        assertNotNull(accept("SELECT dep.* FROM test"));
    }

    public void testGroupByOnInexact() {
        assertEquals("1:36: Field [text] of data type [text] cannot be used for grouping; " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT COUNT(*) FROM test GROUP BY text"));
    }

    public void testGroupByOnNested() {
        assertEquals("1:38: Grouping isn't (yet) compatible with nested fields [dep.dep_id]",
                error("SELECT dep.dep_id FROM test GROUP BY dep.dep_id"));
    }

    public void testHavingOnNested() {
        assertEquals("1:51: HAVING isn't (yet) compatible with nested fields [dep.start_date]",
                error("SELECT int FROM test GROUP BY int HAVING AVG(YEAR(dep.start_date)) > 1980"));
    }

    public void testGroupByScalarFunctionWithAggOnTarget() {
        assertEquals("1:31: Cannot use an aggregate [AVG] for grouping",
                error("SELECT int FROM test GROUP BY AVG(int) + 2"));
    }

    public void testUnsupportedType() {
        assertEquals("1:8: Cannot use field [unsupported] type [ip_range] as is unsupported",
                error("SELECT unsupported FROM test"));
    }

    public void testUnsupportedStarExpansion() {
        assertEquals("1:8: Cannot use field [unsupported] type [ip_range] as is unsupported",
                error("SELECT unsupported.* FROM test"));
    }

    public void testUnsupportedTypeInFilter() {
        assertEquals("1:26: Cannot use field [unsupported] type [ip_range] as is unsupported",
                error("SELECT * FROM test WHERE unsupported > 1"));
    }

    public void testTermEqualitOnInexact() {
        assertEquals("1:26: [text = 'value'] cannot operate on first argument field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT * FROM test WHERE text = 'value'"));
    }

    public void testTermEqualityOnAmbiguous() {
        assertEquals("1:26: [some.ambiguous = 'value'] cannot operate on first argument field of data type [text]: " +
                "Multiple exact keyword candidates available for [ambiguous]; specify which one to use",
            error("SELECT * FROM test WHERE some.ambiguous = 'value'"));
    }

    public void testUnsupportedTypeInFunction() {
        assertEquals("1:12: Cannot use field [unsupported] type [ip_range] as is unsupported",
                error("SELECT ABS(unsupported) FROM test"));
    }

    public void testUnsupportedTypeInOrder() {
        assertEquals("1:29: Cannot use field [unsupported] type [ip_range] as is unsupported",
                error("SELECT * FROM test ORDER BY unsupported"));
    }

    public void testInexactFieldInOrder() {
        assertEquals("1:29: ORDER BY cannot be applied to field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT * FROM test ORDER BY text"));
    }

    public void testGroupByOrderByAggregate() {
        accept("SELECT AVG(int) a FROM test GROUP BY bool ORDER BY a");
    }

    public void testGroupByOrderByAggs() {
        accept("SELECT int FROM test GROUP BY int ORDER BY COUNT(*)");
    }

    public void testGroupByOrderByAggAndGroupedColumn() {
        accept("SELECT int FROM test GROUP BY int ORDER BY int, MAX(int)");
    }

    public void testGroupByOrderByNonAggAndNonGroupedColumn() {
        assertEquals("1:44: Cannot order by non-grouped column [bool], expected [int]",
                error("SELECT int FROM test GROUP BY int ORDER BY bool"));
    }

    public void testGroupByOrderByScore() {
        assertEquals("1:44: Cannot order by non-grouped column [SCORE()], expected [int] or an aggregate function",
                error("SELECT int FROM test GROUP BY int ORDER BY SCORE()"));
    }

    public void testHavingOnColumn() {
        assertEquals("1:42: Cannot use HAVING filter on non-aggregate [int]; use WHERE instead",
                error("SELECT int FROM test GROUP BY int HAVING int > 2"));
    }

    public void testHavingOnScalar() {
        assertEquals("1:42: Cannot use HAVING filter on non-aggregate [int]; use WHERE instead",
                error("SELECT int FROM test GROUP BY int HAVING 2 < ABS(int)"));
    }

    public void testInWithDifferentDataTypes() {
        assertEquals("1:8: 2nd argument of [1 IN (2, '3', 4)] must be [integer], found value ['3'] type [keyword]",
            error("SELECT 1 IN (2, '3', 4)"));
    }

    public void testInWithDifferentDataTypesFromLeftValue() {
        assertEquals("1:8: 1st argument of [1 IN ('foo', 'bar')] must be [integer], found value ['foo'] type [keyword]",
            error("SELECT 1 IN ('foo', 'bar')"));
    }

    public void testInWithFieldInListOfValues() {
        assertEquals("1:26: Comparisons against variables are not (currently) supported; offender [int] in [int IN (1, int)]",
            error("SELECT * FROM test WHERE int IN (1, int)"));
    }

    public void testInOnFieldTextWithNoKeyword() {
        assertEquals("1:26: [IN] cannot operate on field of data type [text]: " +
            "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT * FROM test WHERE text IN ('foo', 'bar')"));
    }

    public void testNotSupportedAggregateOnDate() {
        assertEquals("1:8: argument of [AVG(date)] must be [numeric], found value [date] type [datetime]",
            error("SELECT AVG(date) FROM test"));
    }

    public void testInvalidTypeForStringFunction_WithOneArgString() {
        assertEquals("1:8: argument of [LENGTH(1)] must be [string], found value [1] type [integer]",
            error("SELECT LENGTH(1)"));
    }

    public void testInvalidTypeForStringFunction_WithOneArgNumeric() {
        assertEquals("1:8: argument of [CHAR('foo')] must be [integer], found value ['foo'] type [keyword]",
            error("SELECT CHAR('foo')"));
    }

    public void testInvalidTypeForNestedStringFunctions_WithOneArg() {
        assertEquals("1:14: argument of [CHAR('foo')] must be [integer], found value ['foo'] type [keyword]",
            error("SELECT ASCII(CHAR('foo'))"));
    }

    public void testInvalidTypeForNumericFunction_WithOneArg() {
        assertEquals("1:8: argument of [COS('foo')] must be [numeric], found value ['foo'] type [keyword]",
            error("SELECT COS('foo')"));
    }

    public void testInvalidTypeForBooleanFunction_WithOneArg() {
        assertEquals("1:8: argument of [NOT 'foo'] must be [boolean], found value ['foo'] type [keyword]",
            error("SELECT NOT 'foo'"));
    }

    public void testInvalidTypeForStringFunction_WithTwoArgs() {
        assertEquals("1:8: first argument of [CONCAT] must be [string], found value [1] type [integer]",
            error("SELECT CONCAT(1, 'bar')"));
        assertEquals("1:8: second argument of [CONCAT] must be [string], found value [2] type [integer]",
            error("SELECT CONCAT('foo', 2)"));
    }

    public void testInvalidTypeForNumericFunction_WithTwoArgs() {
        assertEquals("1:8: first argument of [TRUNCATE('foo', 2)] must be [numeric], found value ['foo'] type [keyword]",
            error("SELECT TRUNCATE('foo', 2)"));
        assertEquals("1:8: second argument of [TRUNCATE(1.2, 'bar')] must be [integer], found value ['bar'] type [keyword]",
            error("SELECT TRUNCATE(1.2, 'bar')"));
    }

    public void testInvalidTypeForBooleanFuntion_WithTwoArgs() {
        assertEquals("1:8: first argument of [1 OR true] must be [boolean], found value [1] type [integer]",
            error("SELECT 1 OR true"));
        assertEquals("1:8: second argument of [true OR 2] must be [boolean], found value [2] type [integer]",
            error("SELECT true OR 2"));
    }

    public void testInvalidTypeForReplace() {
        assertEquals("1:8: first argument of [REPLACE(1, 'foo', 'bar')] must be [string], found value [1] type [integer]",
            error("SELECT REPLACE(1, 'foo', 'bar')"));
        assertEquals("1:8: [REPLACE(text, 'foo', 'bar')] cannot operate on first argument field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT REPLACE(text, 'foo', 'bar') FROM test"));

        assertEquals("1:8: second argument of [REPLACE('foo', 2, 'bar')] must be [string], found value [2] type [integer]",
            error("SELECT REPLACE('foo', 2, 'bar')"));
        assertEquals("1:8: [REPLACE('foo', text, 'bar')] cannot operate on second argument field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT REPLACE('foo', text, 'bar') FROM test"));

        assertEquals("1:8: third argument of [REPLACE('foo', 'bar', 3)] must be [string], found value [3] type [integer]",
            error("SELECT REPLACE('foo', 'bar', 3)"));
        assertEquals("1:8: [REPLACE('foo', 'bar', text)] cannot operate on third argument field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT REPLACE('foo', 'bar', text) FROM test"));
    }

    public void testInvalidTypeForSubString() {
        assertEquals("1:8: first argument of [SUBSTRING(1, 2, 3)] must be [string], found value [1] type [integer]",
            error("SELECT SUBSTRING(1, 2, 3)"));
        assertEquals("1:8: [SUBSTRING(text, 2, 3)] cannot operate on first argument field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT SUBSTRING(text, 2, 3) FROM test"));

        assertEquals("1:8: second argument of [SUBSTRING('foo', 'bar', 3)] must be [integer], found value ['bar'] type [keyword]",
            error("SELECT SUBSTRING('foo', 'bar', 3)"));

        assertEquals("1:8: third argument of [SUBSTRING('foo', 2, 'bar')] must be [integer], found value ['bar'] type [keyword]",
            error("SELECT SUBSTRING('foo', 2, 'bar')"));
    }

    public void testInvalidTypeForFunction_WithFourArgs() {
        assertEquals("1:8: first argument of [INSERT(1, 1, 2, 'new')] must be [string], found value [1] type [integer]",
            error("SELECT INSERT(1, 1, 2, 'new')"));
        assertEquals("1:8: second argument of [INSERT('text', 'foo', 2, 'new')] must be [numeric], found value ['foo'] type [keyword]",
            error("SELECT INSERT('text', 'foo', 2, 'new')"));
        assertEquals("1:8: third argument of [INSERT('text', 1, 'bar', 'new')] must be [numeric], found value ['bar'] type [keyword]",
            error("SELECT INSERT('text', 1, 'bar', 'new')"));
        assertEquals("1:8: fourth argument of [INSERT('text', 1, 2, 3)] must be [string], found value [3] type [integer]",
            error("SELECT INSERT('text', 1, 2, 3)"));
    }

    public void testInvalidTypeForLikeMatch() {
        assertEquals("1:26: [text LIKE 'foo'] cannot operate on field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT * FROM test WHERE text LIKE 'foo'"));
    }

    public void testInvalidTypeForRLikeMatch() {
        assertEquals("1:26: [text RLIKE 'foo'] cannot operate on field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT * FROM test WHERE text RLIKE 'foo'"));
    }

    public void testAllowCorrectFieldsInIncompatibleMappings() {
        assertNotNull(incompatibleAccept("SELECT languages FROM \"*\""));
    }

    public void testWildcardInIncompatibleMappings() {
        assertNotNull(incompatibleAccept("SELECT * FROM \"*\""));
    }

    public void testMismatchedFieldInIncompatibleMappings() {
        assertEquals(
                "1:8: Cannot use field [emp_no] due to ambiguities being mapped as [2] incompatible types: "
                        + "[integer] in [basic], [long] in [incompatible]",
                incompatibleError("SELECT emp_no FROM \"*\""));
    }

    public void testMismatchedFieldStarInIncompatibleMappings() {
        assertEquals(
                "1:8: Cannot use field [emp_no] due to ambiguities being mapped as [2] incompatible types: "
                        + "[integer] in [basic], [long] in [incompatible]",
                incompatibleError("SELECT emp_no.* FROM \"*\""));
    }

    public void testMismatchedFieldFilterInIncompatibleMappings() {
        assertEquals(
                "1:33: Cannot use field [emp_no] due to ambiguities being mapped as [2] incompatible types: "
                        + "[integer] in [basic], [long] in [incompatible]",
                incompatibleError("SELECT languages FROM \"*\" WHERE emp_no > 1"));
    }

    public void testMismatchedFieldScalarInIncompatibleMappings() {
        assertEquals(
                "1:45: Cannot use field [emp_no] due to ambiguities being mapped as [2] incompatible types: "
                        + "[integer] in [basic], [long] in [incompatible]",
                incompatibleError("SELECT languages FROM \"*\" ORDER BY SIGN(ABS(emp_no))"));
    }

    public void testConditionalWithDifferentDataTypes() {
        @SuppressWarnings("unchecked")
        String function = randomFrom(IfNull.class, NullIf.class).getSimpleName();
        assertEquals("1:17: 2nd argument of [" + function + "(3, '4')] must be [integer], found value ['4'] type [keyword]",
            error("SELECT 1 = 1 OR " + function + "(3, '4') > 1"));

        @SuppressWarnings("unchecked")
        String arbirtraryArgsFunction = randomFrom(Coalesce.class, Greatest.class, Least.class).getSimpleName();
        assertEquals("1:17: 3rd argument of [" + arbirtraryArgsFunction + "(null, 3, '4')] must be [integer], " +
                "found value ['4'] type [keyword]",
            error("SELECT 1 = 1 OR " + arbirtraryArgsFunction + "(null, 3, '4') > 1"));
    }

    public void testCaseWithNonBooleanConditionExpression() {
        assertEquals("1:8: condition of [WHEN abs(int) THEN 'foo'] must be [boolean], found value [abs(int)] type [integer]",
            error("SELECT CASE WHEN int = 1 THEN 'one' WHEN abs(int) THEN 'foo' END FROM test"));
    }

    public void testCaseWithDifferentResultDataTypes() {
        assertEquals("1:8: result of [WHEN int > 10 THEN 10] must be [keyword], found value [10] type [integer]",
            error("SELECT CASE WHEN int > 20 THEN 'foo' WHEN int > 10 THEN 10 ELSE 'bar' END FROM test"));
    }

    public void testCaseWithDifferentResultAndDefaultValueDataTypes() {
        assertEquals("1:8: ELSE clause of [date] must be [keyword], found value [date] type [datetime]",
            error("SELECT CASE WHEN int > 20 THEN 'foo' ELSE date END FROM test"));
    }

    public void testCaseWithDifferentResultAndDefaultValueDataTypesAndNullTypesSkipped() {
        assertEquals("1:8: ELSE clause of [date] must be [keyword], found value [date] type [datetime]",
            error("SELECT CASE WHEN int > 20 THEN null WHEN int > 10 THEN null WHEN int > 5 THEN 'foo' ELSE date END FROM test"));
    }

    public void testIifWithNonBooleanConditionExpression() {
        assertEquals("1:8: first argument of [IIF(int, 'one', 'zero')] must be [boolean], found value [int] type [integer]",
            error("SELECT IIF(int, 'one', 'zero') FROM test"));
    }

    public void testIifWithDifferentResultAndDefaultValueDataTypes() {
        assertEquals("1:8: third argument of [IIF(int > 20, 'foo', date)] must be [keyword], found value [date] type [datetime]",
            error("SELECT IIF(int > 20, 'foo', date) FROM test"));
    }

    public void testAggsInWhere() {
        assertEquals("1:33: Cannot use WHERE filtering on aggregate function [MAX(int)], use HAVING instead",
                error("SELECT MAX(int) FROM test WHERE MAX(int) > 10 GROUP BY bool"));
    }

    public void testHistogramInFilter() {
        assertEquals("1:63: Cannot filter on grouping function [HISTOGRAM(date, INTERVAL 1 MONTH)], use its argument instead",
                error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test WHERE "
                        + "HISTOGRAM(date, INTERVAL 1 MONTH) > CAST('2000-01-01' AS DATETIME) GROUP BY h"));
    }

    // related https://github.com/elastic/elasticsearch/issues/36853
    public void testHistogramInHaving() {
        assertEquals("1:75: Cannot filter on grouping function [h], use its argument instead",
                error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test GROUP BY h HAVING "
                        + "h > CAST('2000-01-01' AS DATETIME)"));
    }

    public void testGroupByScalarOnTopOfGrouping() {
        assertEquals(
                "1:14: Cannot combine [HISTOGRAM(date, INTERVAL 1 MONTH)] grouping function inside "
                        + "GROUP BY, found [MONTH(HISTOGRAM(date, INTERVAL 1 MONTH))]; consider moving the expression inside the histogram",
                error("SELECT MONTH(HISTOGRAM(date, INTERVAL 1 MONTH)) AS h FROM test GROUP BY h"));
    }

    public void testAggsInHistogram() {
        assertEquals("1:37: Cannot use an aggregate [MAX] for grouping",
                error("SELECT MAX(date) FROM test GROUP BY MAX(int)"));
    }

    public void testGroupingsInHistogram() {
        assertEquals(
                "1:47: Cannot embed grouping functions within each other, found [HISTOGRAM(int, 1)] in [HISTOGRAM(HISTOGRAM(int, 1), 1)]",
                error("SELECT MAX(date) FROM test GROUP BY HISTOGRAM(HISTOGRAM(int, 1), 1)"));
    }

    public void testCastInHistogram() {
        accept("SELECT MAX(date) FROM test GROUP BY HISTOGRAM(CAST(int AS LONG), 1)");
    }

    public void testHistogramNotInGrouping() {
        assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test"));
    }

    public void testHistogramNotInGroupingWithCount() {
        assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h, COUNT(*) FROM test"));
    }

    public void testHistogramNotInGroupingWithMaxFirst() {
        assertEquals("1:19: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                error("SELECT MAX(date), HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test"));
    }

    public void testHistogramWithoutAliasNotInGrouping() {
        assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) FROM test"));
    }

    public void testTwoHistogramsNotInGrouping() {
        assertEquals("1:48: [HISTOGRAM(date, INTERVAL 1 DAY)] needs to be part of the grouping",
                error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h, HISTOGRAM(date, INTERVAL 1 DAY) FROM test GROUP BY h"));
    }

    public void testHistogramNotInGrouping_WithGroupByField() {
        assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) FROM test GROUP BY date"));
    }

    public void testScalarOfHistogramNotInGrouping() {
        assertEquals("1:14: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
                error("SELECT MONTH(HISTOGRAM(date, INTERVAL 1 MONTH)) FROM test"));
    }

    public void testErrorMessageForPercentileWithSecondArgBasedOnAField() {
        assertEquals("1:8: second argument of [PERCENTILE(int, ABS(int))] must be a constant, received [ABS(int)]",
            error("SELECT PERCENTILE(int, ABS(int)) FROM test"));
    }

    public void testErrorMessageForPercentileRankWithSecondArgBasedOnAField() {
        assertEquals("1:8: second argument of [PERCENTILE_RANK(int, ABS(int))] must be a constant, received [ABS(int)]",
            error("SELECT PERCENTILE_RANK(int, ABS(int)) FROM test"));
    }

    public void testTopHitsFirstArgConstant() {
        assertEquals("1:8: first argument of [FIRST('foo', int)] must be a table column, found constant ['foo']",
            error("SELECT FIRST('foo', int) FROM test"));
    }

    public void testTopHitsSecondArgConstant() {
        assertEquals("1:8: second argument of [LAST(int, 10)] must be a table column, found constant [10]",
            error("SELECT LAST(int, 10) FROM test"));
    }

    public void testTopHitsFirstArgTextWithNoKeyword() {
        assertEquals("1:8: [FIRST(text)] cannot operate on first argument field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT FIRST(text) FROM test"));
    }

    public void testTopHitsSecondArgTextWithNoKeyword() {
        assertEquals("1:8: [LAST(keyword, text)] cannot operate on second argument field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT LAST(keyword, text) FROM test"));
    }

    public void testTopHitsGroupByHavingUnsupported() {
        assertEquals("1:50: HAVING filter is unsupported for function [FIRST(int)]",
            error("SELECT FIRST(int) FROM test GROUP BY text HAVING FIRST(int) > 10"));
    }

    public void testMinOnInexactUnsupported() {
        assertEquals("1:8: [MIN(text)] cannot operate on field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT MIN(text) FROM test"));
    }

    public void testMaxOnInexactUnsupported() {
        assertEquals("1:8: [MAX(text)] cannot operate on field of data type [text]: " +
                "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
            error("SELECT MAX(text) FROM test"));
    }

    public void testMinOnKeywordGroupByHavingUnsupported() {
        assertEquals("1:52: HAVING filter is unsupported for function [MIN(keyword)]",
            error("SELECT MIN(keyword) FROM test GROUP BY text HAVING MIN(keyword) > 10"));
    }

    public void testMaxOnKeywordGroupByHavingUnsupported() {
        assertEquals("1:52: HAVING filter is unsupported for function [MAX(keyword)]",
            error("SELECT MAX(keyword) FROM test GROUP BY text HAVING MAX(keyword) > 10"));
    }

    public void testProjectAliasInFilter() {
        accept("SELECT int AS i FROM test WHERE i > 10");
    }

    public void testAggregateAliasInFilter() {
        accept("SELECT int AS i FROM test WHERE i > 10 GROUP BY i HAVING MAX(i) > 10");
    }

    public void testProjectUnresolvedAliasInFilter() {
        assertEquals("1:8: Unknown column [tni]", error("SELECT tni AS i FROM test WHERE i > 10 GROUP BY i"));
    }

    public void testGeoShapeInWhereClause() {
        assertEquals("1:49: geo shapes cannot be used for filtering",
            error("SELECT ST_AsWKT(shape) FROM test WHERE ST_AsWKT(shape) = 'point (10 20)'"));

        // We get only one message back because the messages are grouped by the node that caused the issue
        assertEquals("1:46: geo shapes cannot be used for filtering",
            error("SELECT MAX(ST_X(shape)) FROM test WHERE ST_Y(shape) > 10 GROUP BY ST_GEOMETRYTYPE(shape) ORDER BY ST_ASWKT(shape)"));
    }

    public void testGeoShapeInGroupBy() {
        assertEquals("1:44: geo shapes cannot be used in grouping",
            error("SELECT ST_X(shape) FROM test GROUP BY ST_X(shape)"));
    }

    public void testGeoShapeInOrderBy() {
        assertEquals("1:44: geo shapes cannot be used for sorting",
            error("SELECT ST_X(shape) FROM test ORDER BY ST_Z(shape)"));
    }

    public void testGeoShapeInSelect() {
        accept("SELECT ST_X(shape) FROM test");
    }

    //
    // Pivot verifications
    //
    public void testPivotNonExactColumn() {
        assertEquals("1:72: Field [text] of data type [text] cannot be used for grouping;"
                + " No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
                error("SELECT * FROM (SELECT int, text, keyword FROM test) " + "PIVOT(AVG(int) FOR text IN ('bla'))"));
    }

    public void testPivotColumnUsedInsteadOfAgg() {
        assertEquals("1:59: No aggregate function found in PIVOT at [int]",
                error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(int FOR keyword IN ('bla'))"));
    }

    public void testPivotScalarUsedInsteadOfAgg() {
        assertEquals("1:59: No aggregate function found in PIVOT at [ROUND(int)]",
                error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(ROUND(int) FOR keyword IN ('bla'))"));
    }

    public void testPivotScalarUsedAlongSideAgg() {
        assertEquals("1:59: Non-aggregate function found in PIVOT at [AVG(int) + ROUND(int)]",
                error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) + ROUND(int) FOR keyword IN ('bla'))"));
    }

    public void testPivotValueNotFoldable() {
        assertEquals("1:91: Non-literal [bool] found inside PIVOT values",
                error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) FOR keyword IN ('bla', bool))"));
    }

    public void testPivotWithFunctionInput() {
        assertEquals("1:37: No functions allowed (yet); encountered [YEAR(date)]",
                error("SELECT * FROM (SELECT int, keyword, YEAR(date) FROM test) " + "PIVOT(AVG(int) FOR keyword IN ('bla'))"));
    }

    public void testPivotWithFoldableFunctionInValues() {
        assertEquals("1:85: Non-literal [UCASE('bla')] found inside PIVOT values",
                error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) FOR keyword IN ( UCASE('bla') ))"));
    }

    public void testPivotWithNull() {
        assertEquals("1:85: Null not allowed as a PIVOT value",
                error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) FOR keyword IN ( null ))"));
    }

    public void testPivotValuesHaveDifferentTypeThanColumn() {
        assertEquals("1:81: Literal ['bla'] of type [keyword] does not match type [boolean] of PIVOT column [bool]",
                error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) FOR bool IN ('bla'))"));
    }

    public void testPivotValuesWithMultipleDifferencesThanColumn() {
        assertEquals("1:81: Literal ['bla'] of type [keyword] does not match type [boolean] of PIVOT column [bool]",
                error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) FOR bool IN ('bla', true))"));
    }
}


text:
using System.Drawing;

namespace ScreenCutter.PluginContract
{
    public interface IImageFilter
    {
        Bitmap Filter(Bitmap img);
    }
}