From ab9d0623d4242663cb2e33701dd33ac0c96b07ca Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Tue, 28 Jul 2020 15:21:12 +0200 Subject: [PATCH 01/27] Init version of caput JSON logger --- caPutLogApp/Makefile | 10 + caPutLogApp/caPutJsonLog.dbd | 1 + caPutLogApp/caPutJsonLogShellCommands.cpp | 91 +++ caPutLogApp/caPutJsonLogTask.cpp | 710 ++++++++++++++++++++++ caPutLogApp/caPutJsonLogTask.h | 139 +++++ caPutLogApp/caPutLog.c | 2 +- caPutLogApp/caPutLogAs.c | 75 ++- caPutLogApp/caPutLogAs.h | 4 +- caPutLogApp/caPutLogTask.h | 27 + 9 files changed, 1052 insertions(+), 7 deletions(-) create mode 100644 caPutLogApp/caPutJsonLog.dbd create mode 100644 caPutLogApp/caPutJsonLogShellCommands.cpp create mode 100644 caPutLogApp/caPutJsonLogTask.cpp create mode 100644 caPutLogApp/caPutJsonLogTask.h diff --git a/caPutLogApp/Makefile b/caPutLogApp/Makefile index 17bd8fb..bfaee0d 100644 --- a/caPutLogApp/Makefile +++ b/caPutLogApp/Makefile @@ -21,6 +21,16 @@ INC += caPutLogAs.h DBD += caPutLog.dbd +# Add support for json format and arrays +# This requires EPICS base version 7.0.1 or higher +ifdef BASE_7_0 +caPutLog_SRCS += caPutJsonLogTask.cpp +caPutLog_SRCS += caPutJsonLogShellCommands.cpp +INC += caPutJsonLogTask.h +DBD += caPutJsonLog.dbd +endif + + caPutLog_LIBS += $(EPICS_BASE_IOC_LIBS) caPutLog_SYS_LIBS_WIN32 += ws2_32 diff --git a/caPutLogApp/caPutJsonLog.dbd b/caPutLogApp/caPutJsonLog.dbd new file mode 100644 index 0000000..28fc064 --- /dev/null +++ b/caPutLogApp/caPutJsonLog.dbd @@ -0,0 +1 @@ +registrar(caPutJsonLogRegister) diff --git a/caPutLogApp/caPutJsonLogShellCommands.cpp b/caPutLogApp/caPutJsonLogShellCommands.cpp new file mode 100644 index 0000000..1d32125 --- /dev/null +++ b/caPutLogApp/caPutJsonLogShellCommands.cpp @@ -0,0 +1,91 @@ +/* File: caPutJsonLogShellCommands.cpp + * Author: Matic Pogacnik + * Created: 21.07.2020 + * + * Contains code for a IOC shell configuration commands of the + * JSON CA put logger implementation. + * + * Modification log: + * ---------------- + * v 1.0 + * - Initial version +*/ + +#include +#include +#include +#include + +#include "caPutJsonLogTask.h" + +/* EPICS iocsh shell commands */ +extern "C" +{ + /* Initalisation */ + int caPutJsonLogInit(const char * address, int config){ + CaPutJsonLogTask *logger = CaPutJsonLogTask::getInstance(); + if (logger != nullptr) return logger->initialize(address, config); + else return -1; + } + + static const iocshArg caPutJsonLogInitArg0 = {"address", iocshArgString}; + static const iocshArg caPutJsonLogInitArg1 = {"config", iocshArgInt}; + static const iocshArg *const caPutJsonLogInitArgs[] = { + &caPutJsonLogInitArg0, + &caPutJsonLogInitArg1 + }; + static const iocshFuncDef caPutjsonLogInitDef = {"caPutJsonLogInit", 2, caPutJsonLogInitArgs}; + static void caPutJsonLogInitCall(const iocshArgBuf *args) + { + caPutJsonLogInit(args[0].sval, args[1].ival); + } + + + /* Reconfigure */ + int caPutJsonLogReconf(int config){ + CaPutJsonLogTask *logger = CaPutJsonLogTask::getInstance(); + if (logger != nullptr) return logger->reconfigure(config); + else return -1; + } + + static const iocshArg caPutJsonLogReconfArg0 = {"config", iocshArgInt}; + static const iocshArg *const caPutJsonLogReconfArgs[] = { + &caPutJsonLogReconfArg0 + }; + static const iocshFuncDef caPutJsonLogReconfDef = {"caPutJsonLogReconf", 1, caPutJsonLogReconfArgs}; + static void caPutJsonLogReconfCall(const iocshArgBuf *args) + { + caPutJsonLogReconf(args[0].ival); + } + + /* Report */ + int caPutJsonLogShow(int level){ + CaPutJsonLogTask *logger = CaPutJsonLogTask::getInstance(); + if (logger != nullptr) return logger->report(level); + else return -1; + } + + static const iocshArg caPutJsonLogShowArg0 = {"level", iocshArgInt}; + static const iocshArg *const caPutJsonLogShowArgs[] = { + &caPutJsonLogShowArg0 + }; + static const iocshFuncDef caPutJsonLogShowDef = {"caPutJsonLogShow", 1, caPutJsonLogShowArgs}; + static void caPutJsonLogShowCall(const iocshArgBuf *args) + { + caPutJsonLogShow(args[0].ival); + } + + + /* Register IOCsh commands */ + static void caPutJsonLogRegister(void) + { + static int done = FALSE; + if(done) return; + done = TRUE; + + iocshRegister(&caPutjsonLogInitDef,caPutJsonLogInitCall); + iocshRegister(&caPutJsonLogReconfDef,caPutJsonLogReconfCall); + iocshRegister(&caPutJsonLogShowDef,caPutJsonLogShowCall); + } + epicsExportRegistrar(caPutJsonLogRegister); +} diff --git a/caPutLogApp/caPutJsonLogTask.cpp b/caPutLogApp/caPutJsonLogTask.cpp new file mode 100644 index 0000000..039db57 --- /dev/null +++ b/caPutLogApp/caPutJsonLogTask.cpp @@ -0,0 +1,710 @@ +/* File: caPutJsonLogTask.cpp + * Author: Matic Pogacnik + * Created: 21.07.2020 + * + * Implementation of a CaPutJsonLogTask class. + * For more information refer to the header file. + * + * Modification log: + * ---------------- + * v 1.0 + * - Initial version. +*/ + +// Standard library imports +#include +#include +#include + +// Epics Base imports +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#include + +// This module imports +#include "caPutLogAs.h" +#include "caPutLogTask.h" +#include "caPutJsonLogTask.h" + + +#define isDbrNumeric(type) ((type) > DBR_STRING && (type) <= DBR_ENUM) + +static const ENV_PARAM EPICS_CA_JSON_PUT_LOG_ADDR = {epicsStrDup("EPICS_CA_JSON_PUT_LOG_ADDR"), epicsStrDup("")}; + +CaPutJsonLogTask * CaPutJsonLogTask::instance = NULL; + + +CaPutJsonLogTask *CaPutJsonLogTask::getInstance() noexcept +{ + if (instance == NULL) { + try{ + instance = new CaPutJsonLogTask(); + } + catch (...) { + errlogSevPrintf(errlogMajor, "caPutJsonLog: Failed to construct CA put JSON logger\n"); + return nullptr; + } + } + return instance; +} + +CaPutJsonLogTask::CaPutJsonLogTask() + : caPutJsonLogQ(1000, sizeof(LOGDATA *)), + threadId(NULL), + taskStopper(false), + caPutJsonLogClient(NULL), + pCaPutJsonLogPV(NULL) +{ } + +CaPutJsonLogTask::~CaPutJsonLogTask() +{ } + +int CaPutJsonLogTask::reconfigure(int config) +{ + if ((config < caPutJsonLogNone) + || (config > caPutJsonLogAllNoFilter)) { + errlogSevPrintf(errlogMinor, "caPutJsonLog: invalid config request, setting to default 'caPutJsonLogAll'\n"); + this->config = caPutJsonLogAll; + } else { + this->config = config; + } + return caPutJsonLogSuccess; +} + +int CaPutJsonLogTask::report(int level) +{ + if (this->caPutJsonLogClient != NULL) { + logClientShow(this->caPutJsonLogClient, level); + return caPutJsonLogSuccess; + } + else { + errlogSevPrintf(errlogMinor, "caPutJsonLog: log client not initialised\n"); + return caPutJsonLogError; + } +} + +int CaPutJsonLogTask::initialize(const char* address, int config) +{ + int status; + + // Store passed configuration parameters + this->address = epicsStrDup(address); + this->reconfigure(config); + + // Check if user enabled the logger + if (config == caPutJsonLogNone) { + errlogSevPrintf(errlogInfo, "caPutJsonLog: disabled\n"); + return caPutJsonLogSuccess; + } + + //Configure PV logging + this->configurePvLogging(); + + // Initialize server logging + if (this->caPutJsonLogClient == NULL) { + status = configureServerLogging(address); + if (status != caPutJsonLogSuccess) return status; + } + + // Start logger + status = this->start(); + if (status != caPutJsonLogSuccess) { + return status; + } + + // Initialize caPutLogAs + status = caPutLogAsInit(caddPutToQueue, NULL); + if (status != caPutJsonLogSuccess) { + errlogSevPrintf(errlogMinor, "caPutJsonLog: failed to configure Access security\n"); + return caPutJsonLogError; + } + + // Register exit handler + epicsAtExit(caPutJsonLogExit, NULL); + + return caPutJsonLogSuccess; +} + +int CaPutJsonLogTask::start() +{ + // Check if Access security is enabled + if (!asActive) { + errlogSevPrintf(errlogMajor, "caPutJsonLog: access security disabled, exiting now\n"); + return caPutJsonLogError; + } + + // Create logging thread + this->taskStopper = false; + const char * threadName = "caPutJsonLog"; + threadId = epicsThreadCreate(threadName, + epicsThreadPriorityLow, + epicsThreadGetStackSize(epicsThreadStackSmall), + (EPICSTHREADFUNC) caPutJsonLogWorker, + NULL); + if (!threadId) { + errlogSevPrintf(errlogFatal,"caPutJsonLog: thread creation failed\n"); + return caPutJsonLogError; + } + return caPutJsonLogSuccess; +} + +int CaPutJsonLogTask::stop() +{ + // Send signal to stop the logger worker thread + this->taskStopper = true; + + // Deregister Access Security trap + caPutLogAsStop(); + + return caPutJsonLogSuccess; +} + +int CaPutJsonLogTask::configureServerLogging(const char* address) +{ + int status; + struct sockaddr_in saddr; + + // Parse the address + if (!address || !address[0]) + address = envGetConfigParamPtr(&EPICS_CA_JSON_PUT_LOG_ADDR); + status = aToIPAddr (address, this->default_port, &saddr); + if (status < 0) { + errlogSevPrintf(errlogMinor, "caPutJsonLog: bad address or host name\n"); + return caPutJsonLogError; + } + + // Create log client + this->caPutJsonLogClient = logClientCreate(saddr.sin_addr, ntohs(saddr.sin_port)); + if (this->caPutJsonLogClient == NULL) return caPutJsonLogError; + + return caPutJsonLogSuccess; +} + +int CaPutJsonLogTask::configurePvLogging() +{ + char *caPutJsonLogPVEnv; + + // Get name of a PV to which to log + caPutJsonLogPVEnv = std::getenv("EPICS_AS_PUT_JSON_LOG_PV"); + + // Variable is not set, return success (Logging to a PV is disabled) + if (!caPutJsonLogPVEnv){ + this->pCaPutJsonLogPV = NULL; + return caPutJsonLogSuccess; + } + + // Get pointer to a PV structure + long getpv_st; + this->pCaPutJsonLogPV = &this->caPutJsonLogPV; + getpv_st = dbNameToAddr(caPutJsonLogPVEnv, this->pCaPutJsonLogPV); + + if (getpv_st) { + this->pCaPutJsonLogPV = NULL; + errlogSevPrintf(errlogMajor, + "caPutJsonLog: PV for CA Put Logging not found, logging to PV disabled\n"); + return caPutJsonLogError; + } + return caPutJsonLogSuccess; +} + +void CaPutJsonLogTask::caPutJsonLogTask(void *arg) +{ + + bool sent = false, burst = false; + LOGDATA *pcurrent, *pnext; + VALUE old_value, max_value, min_value; + VALUE *pold=&old_value, *pmax=&max_value, *pmin=&min_value; + + // Receive 1st message + this->caPutJsonLogQ.receive(&pcurrent, sizeof(LOGDATA *)); + std::memcpy(pold, &pcurrent->old_value, sizeof(VALUE)); + + if (pcurrent->new_size > 0 && pcurrent->type != DBF_CHAR) { + std::memcpy(pmax, &pcurrent->new_value.value, sizeof(VALUE)); + std::memcpy(pmin, &pcurrent->new_value.value, sizeof(VALUE)); + } + + // Main loop of the logger, which accepts the caput changes and process them + while (!this->taskStopper) + { + int msgSize; + + // Receive new put with timeout + msgSize = this->caPutJsonLogQ.receive(&pnext, sizeof(LOGDATA *), 5.0); + + /* Timeout */ + if (msgSize == -1) { + // If we have have unsent message and timeout occurred, send the cached change + if (!sent) { + buildJsonMsg(pold, pcurrent, burst, pmin, pmax, this->config); + std::memcpy(pold, &pcurrent->new_value.value, sizeof(VALUE)); + sent = true; + burst = false; + } + } + + // Message is not the correct size + else if (msgSize != sizeof(LOGDATA *)) { + errlogSevPrintf(errlogMinor, "caPutJsonLog: discarding incomplete log data message\n"); + } + + // Previous and new PV are the same and we are applying the burst filter + else if ((pnext->pfield == pcurrent->pfield) + && (this->config != caPutJsonLogAllNoFilter) + && (pcurrent->type != DBR_CHAR)) { + + // Free "old" value + caPutLogDataFree(pcurrent); + pcurrent = pnext; + + // First message after logging + if (sent) { + // Set new initial max & min values + std::memcpy(pmax, &pcurrent->new_value.value, sizeof(VALUE)); + std::memcpy(pmin, &pcurrent->new_value.value, sizeof(VALUE)); + + sent = false; + burst = false; + } + // Multiple puts within timeout + else { + if (isDbrNumeric(pcurrent->type) && pcurrent->new_size == 1) { + burst = true; + calculateMax(pmax, &pcurrent->new_value.value, pmax, pcurrent->type); + calculateMin(pmin, &pcurrent->new_value.value, pmin, pcurrent->type); + } + } + } + + // We log every change + else { + if (!sent) { + buildJsonMsg(pold, pcurrent, burst, pmin, pmax, this->config); + sent = true; + } + + caPutLogDataFree(pcurrent); + pcurrent = pnext; + + /* Set new old_value */ + std::memcpy(pold, &pcurrent->old_value, sizeof(VALUE)); + + /* Set new max & min values */ + std::memcpy(pmax, &pcurrent->new_value.value, sizeof(VALUE)); + std::memcpy(pmin, &pcurrent->new_value.value, sizeof(VALUE)); + + sent = false; + burst = false; + } + } + this->taskStopper = false; + errlogSevPrintf(errlogInfo, "caPutJsonLog: log task exiting\n"); +} + +void CaPutJsonLogTask::addPutToQueue(LOGDATA * plogData) +{ + if (this->caPutJsonLogQ.trySend(&plogData, sizeof(LOGDATA *))) { + errlogSevPrintf(errlogMinor, "caPutJsonLog: message queue overflow\n"); + caPutLogDataFree(plogData); + } +} + + +#define CALL_YAJL_FUNCTION_AND_CHECK_STATUS(flag, call) \ + { \ + flag = call; \ + if (flag != yajl_gen_status_ok) { \ + errlogSevPrintf(errlogMinor, "caPutJsonLog: JSON generation error\n"); \ + return caPutJsonLogError; \ + } \ + } + +int CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const LOGDATA *pLogData, + int burst, const VALUE *pmin, const VALUE *pmax, int config) +{ + // Intermediate message build buffer + // The longest message for the buffer can occur in the lso/lsi records which + // is defined with MAX_ARRAY_SIZE_BYTES, if this is less then 40 then + // stringin / stringout are the limits + size_t interBufferSize = MAX_STRING_SIZE + 1 > MAX_ARRAY_SIZE_BYTES + 1 + ? MAX_STRING_SIZE + 1 + : MAX_ARRAY_SIZE_BYTES + 1; + unsigned char interBuffer[interBufferSize]; + yajl_gen_status status; + + // Configure yajl generator + yajl_gen handle = yajl_gen_alloc(NULL); + if (handle == NULL) { + errlogSevPrintf(errlogMinor, "caPutJsonLog: failed to allocate yajl handler\n"); + return caPutJsonLogError; + } + + // Open json root map + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_map_open(handle)); + + // Add date parameter + const unsigned char str_date[] = "date"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_date, + strlen(reinterpret_cast(str_date)))); + epicsTimeToStrftime(reinterpret_cast(interBuffer), interBufferSize, "%d-%m-%Y", + &pLogData->new_value.time); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, + strlen(reinterpret_cast(interBuffer)))); + + // Add time of the day parameter + const unsigned char str_time[] = "time"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_time, + strlen(reinterpret_cast(str_time)))); + epicsTimeToStrftime(reinterpret_cast(interBuffer), interBufferSize, "%H:%M:%S", + &pLogData->new_value.time); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, + strlen(reinterpret_cast(interBuffer)))); + + // Add hostname + const unsigned char str_host[] = "host"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_host, + strlen(reinterpret_cast(str_host)))); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, + reinterpret_cast(pLogData->hostid), + strlen(pLogData->hostid))); + + // Add system user + const unsigned char str_user[] = "user"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_user, + strlen(reinterpret_cast(str_user)))); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, + reinterpret_cast(pLogData->userid), + strlen(pLogData->userid))); + + // Add PV name + const unsigned char str_pvName[] = "pv"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_pvName, + strlen(reinterpret_cast(str_pvName)))); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, + reinterpret_cast(pLogData->pv_name), + strlen(pLogData->pv_name))); + + // Add new PV value */ + const unsigned char str_newVal[] = "new"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_newVal, + strlen(reinterpret_cast(str_newVal)))); + + // Open Json array if we have array value + if (pLogData->new_size > 1 && pLogData->type != DBR_CHAR) { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_array_open(handle)); + } + // We have empty array + if (pLogData->new_size == 0) { + const unsigned char empty_value[] = ""; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, empty_value, + strlen(reinterpret_cast(empty_value)))); + } + // We have string + else if (pLogData->type == DBF_CHAR){ + fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, + &pLogData->new_value.value, DBF_CHAR, 0); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, + strlen(reinterpret_cast(interBuffer)))); + } + // Arrays and scalars (all except DBF_CHAR) + else { + for (int i = 0; i < pLogData->new_log_size; i++) { + fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, + &pLogData->new_value.value, pLogData->type, i); + if (pLogData->type == DBF_STRING) { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, + strlen(reinterpret_cast(interBuffer)))); + } else { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_number(handle, + reinterpret_cast(interBuffer), + strlen(reinterpret_cast(interBuffer)))); + } + } + } + // Close Json array if we have array value + if (pLogData->new_size > 1 && pLogData->type != DBR_CHAR) { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_array_close(handle)); + } + // Add new size, but only if we have array + if (pLogData->new_size > 1) { + const unsigned char str_newSize[] = "new-size"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_newSize, + strlen(reinterpret_cast(str_newSize)))); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_integer(handle, pLogData->new_size)); + } + + // Add old PV value */ + const unsigned char str_oldVal[] = "old"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_oldVal, + strlen(reinterpret_cast(str_oldVal)))); + // Open Json array if we have array value + if (pLogData->old_size > 1 && pLogData->type != DBR_CHAR) { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_array_open(handle)); + } + // We have empty array + if (pLogData->old_size == 0) { + const unsigned char empty_value[] = ""; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, empty_value, + strlen(reinterpret_cast(empty_value)))); + } + // We have string + else if (pLogData->type == DBF_CHAR){ + fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, + pold_value, DBF_CHAR, 0); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, + strlen(reinterpret_cast(interBuffer)))); + } + // Arrays and scalars (all except DBF_CHAR) + else { + for (int i = 0; i < pLogData->old_log_size; i++) { + fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, + pold_value, pLogData->type, i); + if (pLogData->type == DBF_STRING) { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, + strlen(reinterpret_cast(interBuffer)));); + } else { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_number(handle, + reinterpret_cast(interBuffer), + strlen(reinterpret_cast(interBuffer)))); + } + } + } + // Close Json array if we have array value + if (pLogData->old_size > 1 && pLogData->type != DBR_CHAR) { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_array_close(handle)); + } + // Add new size, but only if we have array + if (pLogData->old_size > 1) { + const unsigned char str_oldSize[] = "old-size"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_oldSize, + strlen(reinterpret_cast(str_oldSize)))); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_integer(handle, pLogData->old_size)); + } + + // Add minium and maximum values in case of a burst + if (burst && isDbrNumeric(pLogData->type) + && pLogData->old_size == 1 + && pLogData->new_size == 1) { + // Add min value + const unsigned char str_minVal[] = "min"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_minVal, + strlen(reinterpret_cast(str_minVal)))); + fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, + pmin, pLogData->type, 0); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_number(handle, + reinterpret_cast(interBuffer), + strlen(reinterpret_cast(interBuffer)))); + + // Add max value + const unsigned char str_maxVal[] = "max"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_maxVal, + strlen(reinterpret_cast(str_maxVal)));); + fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, + pmax, pLogData->type, 0); + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_number(handle, + reinterpret_cast(interBuffer), + strlen(reinterpret_cast(interBuffer)))); + } + + /* Close root map */ + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_map_close(handle)); + + /* Get JSON as NULL terminated cstring */ + const unsigned char * buf; + size_t len = 0; + yajl_gen_get_buf(handle, &buf, &len); + + /* Get a JSON as a string */ + std::string json (reinterpret_cast(buf)); + + /* Log */ + this->logToServer(json); + this->logToPV(json); +} + +void CaPutJsonLogTask::logToServer(std::string &msg) +{ + if (caPutJsonLogClient) { + logClientSend(caPutJsonLogClient, msg.append("\n").c_str()); + } +} + +void CaPutJsonLogTask::logToPV(std::string &msg) +{ + if (this->pCaPutJsonLogPV != NULL) { + long status; + + status = dbPutField(this->pCaPutJsonLogPV, DBR_CHAR, msg.c_str(), msg.length()); + + if (status) { + errlogSevPrintf(errlogMajor, + "caPutJsonLog: dbPutField to Log PV failed, status = %ld\n", status); + } + } +} + +void CaPutJsonLogTask::calculateMin(VALUE *pres, const VALUE *pa, const VALUE *pb, short type) +{ + switch (type) { + case DBR_CHAR: + pres->v_int8 = std::min(pa->v_int8, pb->v_int8); + return; + case DBR_UCHAR: + pres->v_uint8 = std::min(pa->v_uint8, pb->v_uint8); + return; + case DBR_SHORT: + pres->v_int16 = std::min(pa->v_int16, pb->v_int16); + return; + case DBR_USHORT: + case DBR_ENUM: + pres->v_uint16 = std::min(pa->v_uint16, pb->v_uint16); + return; + case DBR_LONG: + pres->v_int32 = std::min(pa->v_int32, pb->v_int32); + return; + case DBR_ULONG: + pres->v_uint32 = std::min(pa->v_uint32, pb->v_uint32); + return; + case DBR_INT64: + pres->v_int64 = std::min(pa->v_int64, pb->v_int64); + return; + case DBR_UINT64: + pres->v_uint64 = std::min(pa->v_uint64, pb->v_uint64); + return; + case DBR_FLOAT: + pres->v_float = std::min(pa->v_float, pb->v_float); + return; + case DBR_DOUBLE: + pres->v_double = std::min(pa->v_double, pb->v_double); + return; + } +} + +void CaPutJsonLogTask::calculateMax(VALUE *pres, const VALUE *pa, const VALUE *pb, short type) +{ + switch (type) { + case DBR_CHAR: + pres->v_int8 = std::max(pa->v_int8, pb->v_int8); + return; + case DBR_UCHAR: + pres->v_uint8 = std::max(pa->v_uint8, pb->v_uint8); + return; + case DBR_SHORT: + pres->v_int16 = std::max(pa->v_int16, pb->v_int16); + return; + case DBR_USHORT: + case DBR_ENUM: + pres->v_uint16 = std::max(pa->v_uint16, pb->v_uint16); + return; + case DBR_LONG: + pres->v_int32 = std::max(pa->v_int32, pb->v_int32); + return; + case DBR_ULONG: + pres->v_uint32 = std::max(pa->v_uint32, pb->v_uint32); + return; + case DBR_INT64: + pres->v_int64 = std::max(pa->v_int64, pb->v_int64); + return; + case DBR_UINT64: + pres->v_uint64 = std::max(pa->v_uint64, pb->v_uint64); + return; + case DBR_FLOAT: + pres->v_float = std::max(pa->v_float, pb->v_float); + return; + case DBR_DOUBLE: + pres->v_double = std::max(pa->v_double, pb->v_double); + return; + } +} + +bool CaPutJsonLogTask::compareValue(const VALUE *pa, const VALUE *pb, short type) +{ + switch (type) { + case DBR_CHAR: + return (pa->v_int8 == pb->v_int8); + case DBR_UCHAR: + return (pa->v_uint8 == pb->v_uint8); + case DBR_SHORT: + return (pa->v_int16 == pb->v_int16); + case DBR_USHORT: + case DBR_ENUM: + return (pa->v_uint16 == pb->v_uint16); + case DBR_LONG: + return (pa->v_int32 == pb->v_int32); + case DBR_ULONG: + return (pa->v_uint32 == pb->v_uint32); + case DBR_INT64: + return (pa->v_int64 == pb->v_int64); + case DBR_UINT64: + return (pa->v_uint64 == pb->v_uint64); + case DBR_FLOAT: + return (pa->v_float == pb->v_float); + case DBR_DOUBLE: + return (pa->v_double == pb->v_double); + case DBR_STRING: + return (0 == strcmp(pa->v_string, pb->v_string)); + default: + return 0; + } +} + +int CaPutJsonLogTask::fieldVal2Str(char *pbuf, size_t buflen, const VALUE *pval, short type, int index) +{ + switch (type) { + case DBR_CHAR: + return epicsSnprintf(pbuf, buflen, "%s", ((char *)pval)); + case DBR_UCHAR: + return epicsSnprintf(pbuf, buflen, "%d", ((epicsUInt8 *)pval)[index]); + case DBR_SHORT: + return epicsSnprintf(pbuf, buflen, "%hd", ((epicsInt16 *)pval)[index]); + case DBR_USHORT: + case DBR_ENUM: + return epicsSnprintf(pbuf, buflen, "%hu", ((epicsUInt16 *)pval)[index]); + case DBR_LONG: + return epicsSnprintf(pbuf, buflen, "%d", ((epicsInt32 *)pval)[index]); + case DBR_ULONG: + return epicsSnprintf(pbuf, buflen, "%u", ((epicsUInt32 *)pval)[index]); + case DBR_FLOAT: + return epicsSnprintf(pbuf, buflen, "%g", ((epicsFloat32 *)pval)[index]); + case DBR_DOUBLE: + return epicsSnprintf(pbuf, buflen, "%g", ((epicsFloat64 *)pval)[index]); + case DBR_INT64: + return epicsSnprintf(pbuf, buflen, "%lld", ((epicsInt64 *)pval)[index]); + case DBR_UINT64: + return epicsSnprintf(pbuf, buflen, "%llu", ((epicsUInt64 *)pval)[index]); + case DBR_STRING: + return epicsSnprintf(pbuf, buflen, "%s", ((char *)pval) + index * MAX_STRING_SIZE); + default: + errlogSevPrintf(errlogMajor, + "caPutJsonLog: failed to convert PV value to a string representation\n"); + return caPutJsonLogError; + } +} + + +/* Called from C */ + +void caddPutToQueue(LOGDATA * plogData) +{ + CaPutJsonLogTask *instance = CaPutJsonLogTask::getInstance(); + return instance->addPutToQueue(plogData); +} +void caPutJsonLogWorker(void *arg) +{ + CaPutJsonLogTask *instance = CaPutJsonLogTask::getInstance(); + instance->caPutJsonLogTask(arg); +} +void caPutJsonLogExit(void *arg) +{ + CaPutJsonLogTask *instance = CaPutJsonLogTask::getInstance(); + instance->stop(); +} diff --git a/caPutLogApp/caPutJsonLogTask.h b/caPutLogApp/caPutJsonLogTask.h new file mode 100644 index 0000000..12643fb --- /dev/null +++ b/caPutLogApp/caPutJsonLogTask.h @@ -0,0 +1,139 @@ +/* File: caPutJsonLogTask.h + * Author: Matic Pogacnik + * Created: 21.07.2020 + * + * Contains definitions of a CaPutJsonLogTask which is capable of logging CA put + * changes to a remote server and a PV in a JSON format. + * + * Modification log: + * ---------------- + * v 1.0 + * - Initial version +*/ + +#ifndef INCcaPutJsonLogh +#define INCcaPutJsonLogh 1 + +// Standard library imports +#include +#include + +// Epics base imports +#include +#include +#include +#include +#include +#include +#include + +// This module imports +#include "caPutLogTask.h" + +// Status return values +#define caPutJsonLogSuccess 0 +#define caPutJsonLogError -1 + +// For parameter 'config' of caPutJsonLogInit and caPutJsonLogReconfigure +#define caPutJsonLogNone -1 /* no logging (disable) */ +#define caPutJsonLogOnChange 0 /* log only on value change */ +#define caPutJsonLogAll 1 /* log all puts */ +#define caPutJsonLogAllNoFilter 2 /* log all puts no filtering on same PV*/ + +#define FREE_LIST_SIZE 1000 + +#ifdef __cplusplus + +class CaPutJsonLogTask { +public: + /* + * Data + */ + static const int default_port = 7011; + + + /* + * Methods + */ + // Class methods + static CaPutJsonLogTask* getInstance() noexcept; + int initialize(const char* address, int config); + void addPutToQueue(LOGDATA * plogData); + void caPutJsonLogTask(void *arg); //Must be public, called from C + + int start(); + int stop(); + + // Logger config methods + int reconfigure(int config); + int report(int level); + +private: + /* + * Data + */ + // Class variables + static CaPutJsonLogTask *instance; + + // Logger configuration + const char * address; + std::atomic_int config; + + // Interthread communication + epicsMessageQueue caPutJsonLogQ; + + // Working thread + epicsThreadId threadId; + std::atomic_bool taskStopper; + + //Logging to a server + logClientId caPutJsonLogClient; + + // Logging to a PV + DBADDR caPutJsonLogPV; + DBADDR *pCaPutJsonLogPV; + + /* + * Methods + */ + // Class methods (Do not allow public constructors - class is designed as singleton) + CaPutJsonLogTask(); + virtual ~CaPutJsonLogTask(); + CaPutJsonLogTask(const CaPutJsonLogTask&); + CaPutJsonLogTask(const CaPutJsonLogTask&&); + + // Logger logic + int buildJsonMsg(const VALUE *pold_value, const LOGDATA *pLogData, + int burst, const VALUE *pmin, const VALUE *pmax, int config); + + // Logging to a server + int configureServerLogging(const char* address); + void logToServer(std::string &msg); + + // Logging to a PV + int configurePvLogging(); + void logToPV(std::string &msg); + + // Logger helper methods + void calculateMin(VALUE *pres, const VALUE *pa, const VALUE *pb, short type); + void calculateMax(VALUE *pres, const VALUE *pa, const VALUE *pb, short type); + bool compareValue(const VALUE *pa, const VALUE *pb, short type); + int fieldVal2Str(char *pbuf, size_t buflen, const VALUE *pval, short type, int index); +}; + + +extern "C" { +#endif /*__cplusplus */ + +/* + * C interface functions (Called from base) + */ +void caddPutToQueue(LOGDATA * plogData); +void caPutJsonLogWorker(void *arg); +void caPutJsonLogExit(void *arg); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ + +#endif /* INCcaPutJsonLogh */ diff --git a/caPutLogApp/caPutLog.c b/caPutLogApp/caPutLog.c index eb7b3ed..abd00fc 100644 --- a/caPutLogApp/caPutLog.c +++ b/caPutLogApp/caPutLog.c @@ -89,7 +89,7 @@ int caPutLogInit (const char *addr_str, int config) return caPutLogError; } - status = caPutLogAsInit(); + status = caPutLogAsInit(caPutLogTaskSend, caPutLogTaskStop); if (status) { return caPutLogError; } diff --git a/caPutLogApp/caPutLogAs.c b/caPutLogApp/caPutLogAs.c index 4a6b966..e16e2ed 100644 --- a/caPutLogApp/caPutLogAs.c +++ b/caPutLogApp/caPutLogAs.c @@ -68,9 +68,15 @@ static void *logDataFreeList = 0; #define FREE_LIST_SIZE 1000 static void caPutLogAs(asTrapWriteMessage * pmessage, int afterPut); +static void (*psendCallback)(LOGDATA *); +static void (*pstopCallback)() = NULL; -int caPutLogAsInit() + +int caPutLogAsInit(void (*sendCallback)(LOGDATA *), void (*stopCallback)()) { + psendCallback = sendCallback; + pstopCallback = stopCallback; + if (!asActive) { errlogSevPrintf(errlogFatal, "caPutLog: access security is disabled\n"); return caPutLogError; @@ -92,7 +98,9 @@ int caPutLogAsInit() void caPutLogAsStop() { - caPutLogTaskStop(); + if (pstopCallback != NULL) { + pstopCallback(); + } if (listenerId) { asTrapWriteUnregisterListener(listenerId); @@ -113,6 +121,9 @@ static void caPutLogAs(asTrapWriteMessage *pmessage, int afterPut) LOGDATA *plogData; long options, num_elm; long status; + rset *prset; + long noElements = 0; + long offset = 0; if (!afterPut) { /* before put */ plogData = caPutLogDataCalloc(); @@ -140,9 +151,17 @@ static void caPutLogAs(asTrapWriteMessage *pmessage, int afterPut) plogData->pfield = paddr->pfield; options = 0; - num_elm = 1; + num_elm = caPutLogMaxArraySize(plogData->type); status = dbGetField( paddr, plogData->type, &plogData->old_value, &options, &num_elm, 0); + plogData->old_log_size = num_elm; + if ((prset = dbGetRset(paddr)) && + prset->get_array_info) { + prset->get_array_info(paddr, &noElements, &offset); + plogData->old_size = noElements; + } else { + plogData->old_size = num_elm; + } if (status) { errlogPrintf("caPutLog: dbGetField error=%ld\n", status); @@ -156,9 +175,18 @@ static void caPutLogAs(asTrapWriteMessage *pmessage, int afterPut) plogData = (LOGDATA *) pmessage->userPvt; options = DBR_TIME; - num_elm = 1; + num_elm = caPutLogMaxArraySize(plogData->type); status = dbGetField( paddr, plogData->type, &plogData->new_value, &options, &num_elm, 0); + plogData->new_log_size = num_elm; + if ((prset = dbGetRset(paddr)) && + prset->get_array_info) { + prset->get_array_info(paddr, &noElements, &offset); + plogData->new_size = noElements; + } else { + plogData->new_size = num_elm; + } + if (status) { errlogPrintf("caPutLog: dbGetField error=%ld.\n", status); plogData->type = DBR_STRING; @@ -170,10 +198,47 @@ static void caPutLogAs(asTrapWriteMessage *pmessage, int afterPut) plogData->new_value.time.secPastEpoch = curTime.secPastEpoch; plogData->new_value.time.nsec = curTime.nsec; } - caPutLogTaskSend(plogData); + psendCallback(plogData); } } +int caPutLogMaxArraySize(short type) +{ + +#if !JSON_AND_ARRAYS_SUPPORTED + return 1; +#else + switch (type) { + case DBR_CHAR: + return MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt8); + case DBR_UCHAR: + return MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt8); + case DBR_SHORT: + return MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt16); + case DBR_USHORT: + case DBR_ENUM: + return MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt16); + case DBR_LONG: + return MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt32); + case DBR_ULONG: + return MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt32); + #ifdef DBR_INT64 + case DBR_INT64: + return MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt64); + case DBR_UINT64: + return MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt64); + #endif + case DBR_FLOAT: + return MAX_ARRAY_SIZE_BYTES/sizeof(epicsFloat32); + case DBR_DOUBLE: + return MAX_ARRAY_SIZE_BYTES/sizeof(epicsFloat64); + case DBR_STRING: + return MAX_ARRAY_SIZE_BYTES/MAX_STRING_SIZE; + + } +#endif +} + void caPutLogDataFree(LOGDATA *plogData) { freeListFree(logDataFreeList, plogData); diff --git a/caPutLogApp/caPutLogAs.h b/caPutLogApp/caPutLogAs.h index c35263c..00d0d6a 100644 --- a/caPutLogApp/caPutLogAs.h +++ b/caPutLogApp/caPutLogAs.h @@ -9,11 +9,13 @@ extern "C" { #endif -epicsShareFunc int caPutLogAsInit(); +epicsShareFunc int caPutLogAsInit(void (*sendCallback)(LOGDATA *), void (*stopCallback)()); epicsShareFunc void caPutLogAsStop(); epicsShareFunc void caPutLogDataFree(LOGDATA *pLogData); epicsShareFunc LOGDATA* caPutLogDataCalloc(void); +int caPutLogMaxArraySize(short type); + #ifdef __cplusplus } #endif diff --git a/caPutLogApp/caPutLogTask.h b/caPutLogApp/caPutLogTask.h index ae005cf..65eaf2d 100644 --- a/caPutLogApp/caPutLogTask.h +++ b/caPutLogApp/caPutLogTask.h @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -12,6 +13,14 @@ extern "C" { #define MAX_USERID_SIZE 32 #define MAX_HOSTID_SIZE 32 +#define JSON_AND_ARRAYS_SUPPORTED (EPICS_VERSION * 10000 + EPICS_REVISION * 100 + EPICS_MODIFICATION >= 70001) + +#if JSON_AND_ARRAYS_SUPPORTED +#define MAX_ARRAY_SIZE_BYTES 400 +#else +#define MAX_ARRAY_SIZE_BYTES 0 +#endif + typedef union { epicsInt8 v_int8; epicsUInt8 v_uint8; @@ -26,6 +35,20 @@ typedef union { epicsFloat32 v_float; epicsFloat64 v_double; char v_string[MAX_STRING_SIZE]; + +#if JSON_AND_ARRAYS_SUPPORTED + epicsInt8 a_int8[MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt8)]; + epicsUInt8 a_uint8[MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt8)]; + epicsInt16 a_int16[MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt16)]; + epicsUInt16 a_uint16[MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt16)]; + epicsInt32 a_int32[MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt32)]; + epicsUInt32 a_uint32[MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt32)]; + epicsInt64 a_int64[MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt64)]; + epicsUInt64 a_uint64[MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt64)]; + epicsFloat32 a_float[MAX_ARRAY_SIZE_BYTES/sizeof(epicsFloat32)]; + epicsFloat64 a_double[MAX_ARRAY_SIZE_BYTES/sizeof(epicsFloat64)]; + char a_string[MAX_ARRAY_SIZE_BYTES/MAX_STRING_SIZE][MAX_STRING_SIZE]; +#endif } VALUE; typedef struct { @@ -39,6 +62,10 @@ typedef struct { TS_STAMP time; VALUE value; } new_value; + int old_size; + int old_log_size; + int new_size; + int new_log_size; } LOGDATA; epicsShareFunc int caPutLogTaskStart(int config); From 6a30b46e9cad24b7b5b780be7b6dbc94812cbf3d Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Fri, 31 Jul 2020 15:38:51 +0200 Subject: [PATCH 02/27] Switch from defines to enums --- caPutLogApp/caPutJsonLogShellCommands.cpp | 8 ++--- caPutLogApp/caPutJsonLogTask.cpp | 27 ++++++++-------- caPutLogApp/caPutJsonLogTask.h | 39 ++++++++++++----------- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/caPutLogApp/caPutJsonLogShellCommands.cpp b/caPutLogApp/caPutJsonLogShellCommands.cpp index 1d32125..26e7669 100644 --- a/caPutLogApp/caPutJsonLogShellCommands.cpp +++ b/caPutLogApp/caPutJsonLogShellCommands.cpp @@ -22,7 +22,7 @@ extern "C" { /* Initalisation */ - int caPutJsonLogInit(const char * address, int config){ + int caPutJsonLogInit(const char * address, caPutJsonLogConfig config){ CaPutJsonLogTask *logger = CaPutJsonLogTask::getInstance(); if (logger != nullptr) return logger->initialize(address, config); else return -1; @@ -37,12 +37,12 @@ extern "C" static const iocshFuncDef caPutjsonLogInitDef = {"caPutJsonLogInit", 2, caPutJsonLogInitArgs}; static void caPutJsonLogInitCall(const iocshArgBuf *args) { - caPutJsonLogInit(args[0].sval, args[1].ival); + caPutJsonLogInit(args[0].sval, static_cast(args[1].ival)); } /* Reconfigure */ - int caPutJsonLogReconf(int config){ + int caPutJsonLogReconf(caPutJsonLogConfig config){ CaPutJsonLogTask *logger = CaPutJsonLogTask::getInstance(); if (logger != nullptr) return logger->reconfigure(config); else return -1; @@ -55,7 +55,7 @@ extern "C" static const iocshFuncDef caPutJsonLogReconfDef = {"caPutJsonLogReconf", 1, caPutJsonLogReconfArgs}; static void caPutJsonLogReconfCall(const iocshArgBuf *args) { - caPutJsonLogReconf(args[0].ival); + caPutJsonLogReconf(static_cast(args[0].ival)); } /* Report */ diff --git a/caPutLogApp/caPutJsonLogTask.cpp b/caPutLogApp/caPutJsonLogTask.cpp index 039db57..4aeb41f 100644 --- a/caPutLogApp/caPutJsonLogTask.cpp +++ b/caPutLogApp/caPutJsonLogTask.cpp @@ -68,7 +68,7 @@ CaPutJsonLogTask::CaPutJsonLogTask() CaPutJsonLogTask::~CaPutJsonLogTask() { } -int CaPutJsonLogTask::reconfigure(int config) +caPutJsonLogStatus CaPutJsonLogTask::reconfigure(caPutJsonLogConfig config) { if ((config < caPutJsonLogNone) || (config > caPutJsonLogAllNoFilter)) { @@ -80,7 +80,7 @@ int CaPutJsonLogTask::reconfigure(int config) return caPutJsonLogSuccess; } -int CaPutJsonLogTask::report(int level) +caPutJsonLogStatus CaPutJsonLogTask::report(int level) { if (this->caPutJsonLogClient != NULL) { logClientShow(this->caPutJsonLogClient, level); @@ -92,9 +92,9 @@ int CaPutJsonLogTask::report(int level) } } -int CaPutJsonLogTask::initialize(const char* address, int config) +caPutJsonLogStatus CaPutJsonLogTask::initialize(const char* address, caPutJsonLogConfig config) { - int status; + caPutJsonLogStatus status; // Store passed configuration parameters this->address = epicsStrDup(address); @@ -122,7 +122,7 @@ int CaPutJsonLogTask::initialize(const char* address, int config) } // Initialize caPutLogAs - status = caPutLogAsInit(caddPutToQueue, NULL); + status = static_cast(caPutLogAsInit(caddPutToQueue, NULL)); if (status != caPutJsonLogSuccess) { errlogSevPrintf(errlogMinor, "caPutJsonLog: failed to configure Access security\n"); return caPutJsonLogError; @@ -134,7 +134,7 @@ int CaPutJsonLogTask::initialize(const char* address, int config) return caPutJsonLogSuccess; } -int CaPutJsonLogTask::start() +caPutJsonLogStatus CaPutJsonLogTask::start() { // Check if Access security is enabled if (!asActive) { @@ -157,7 +157,7 @@ int CaPutJsonLogTask::start() return caPutJsonLogSuccess; } -int CaPutJsonLogTask::stop() +caPutJsonLogStatus CaPutJsonLogTask::stop() { // Send signal to stop the logger worker thread this->taskStopper = true; @@ -168,7 +168,7 @@ int CaPutJsonLogTask::stop() return caPutJsonLogSuccess; } -int CaPutJsonLogTask::configureServerLogging(const char* address) +caPutJsonLogStatus CaPutJsonLogTask::configureServerLogging(const char* address) { int status; struct sockaddr_in saddr; @@ -189,7 +189,7 @@ int CaPutJsonLogTask::configureServerLogging(const char* address) return caPutJsonLogSuccess; } -int CaPutJsonLogTask::configurePvLogging() +caPutJsonLogStatus CaPutJsonLogTask::configurePvLogging() { char *caPutJsonLogPVEnv; @@ -245,7 +245,7 @@ void CaPutJsonLogTask::caPutJsonLogTask(void *arg) if (msgSize == -1) { // If we have have unsent message and timeout occurred, send the cached change if (!sent) { - buildJsonMsg(pold, pcurrent, burst, pmin, pmax, this->config); + buildJsonMsg(pold, pcurrent, burst, pmin, pmax); std::memcpy(pold, &pcurrent->new_value.value, sizeof(VALUE)); sent = true; burst = false; @@ -288,7 +288,7 @@ void CaPutJsonLogTask::caPutJsonLogTask(void *arg) // We log every change else { if (!sent) { - buildJsonMsg(pold, pcurrent, burst, pmin, pmax, this->config); + buildJsonMsg(pold, pcurrent, burst, pmin, pmax); sent = true; } @@ -328,8 +328,8 @@ void CaPutJsonLogTask::addPutToQueue(LOGDATA * plogData) } \ } -int CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const LOGDATA *pLogData, - int burst, const VALUE *pmin, const VALUE *pmax, int config) +caPutJsonLogStatus CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const LOGDATA *pLogData, + bool burst, const VALUE *pmin, const VALUE *pmax) { // Intermediate message build buffer // The longest message for the buffer can occur in the lso/lsi records which @@ -529,6 +529,7 @@ int CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const LOGDATA *pLogD /* Log */ this->logToServer(json); this->logToPV(json); + return caPutJsonLogSuccess; } void CaPutJsonLogTask::logToServer(std::string &msg) diff --git a/caPutLogApp/caPutJsonLogTask.h b/caPutLogApp/caPutJsonLogTask.h index 12643fb..8187d63 100644 --- a/caPutLogApp/caPutJsonLogTask.h +++ b/caPutLogApp/caPutJsonLogTask.h @@ -31,16 +31,19 @@ #include "caPutLogTask.h" // Status return values -#define caPutJsonLogSuccess 0 -#define caPutJsonLogError -1 +enum caPutJsonLogStatus { + caPutJsonLogSuccess = 0, + caPutJsonLogError = -1 +}; -// For parameter 'config' of caPutJsonLogInit and caPutJsonLogReconfigure -#define caPutJsonLogNone -1 /* no logging (disable) */ -#define caPutJsonLogOnChange 0 /* log only on value change */ -#define caPutJsonLogAll 1 /* log all puts */ -#define caPutJsonLogAllNoFilter 2 /* log all puts no filtering on same PV*/ +// Configuration options +enum caPutJsonLogConfig { + caPutJsonLogNone = -1, /* no logging (disable) */ + caPutJsonLogOnChange = 0, /* log only on value change */ + caPutJsonLogAll = 1, /* log all puts */ + caPutJsonLogAllNoFilter = 2 /* log all puts no filtering on same PV*/ +}; -#define FREE_LIST_SIZE 1000 #ifdef __cplusplus @@ -57,16 +60,16 @@ class CaPutJsonLogTask { */ // Class methods static CaPutJsonLogTask* getInstance() noexcept; - int initialize(const char* address, int config); + caPutJsonLogStatus initialize(const char* address, caPutJsonLogConfig config); void addPutToQueue(LOGDATA * plogData); void caPutJsonLogTask(void *arg); //Must be public, called from C - int start(); - int stop(); + caPutJsonLogStatus start(); + caPutJsonLogStatus stop(); // Logger config methods - int reconfigure(int config); - int report(int level); + caPutJsonLogStatus reconfigure(caPutJsonLogConfig config); + caPutJsonLogStatus report(int level); private: /* @@ -103,22 +106,22 @@ class CaPutJsonLogTask { CaPutJsonLogTask(const CaPutJsonLogTask&&); // Logger logic - int buildJsonMsg(const VALUE *pold_value, const LOGDATA *pLogData, - int burst, const VALUE *pmin, const VALUE *pmax, int config); + caPutJsonLogStatus buildJsonMsg(const VALUE *pold_value, const LOGDATA *pLogData, + bool burst, const VALUE *pmin, const VALUE *pmax); // Logging to a server - int configureServerLogging(const char* address); + caPutJsonLogStatus configureServerLogging(const char* address); void logToServer(std::string &msg); // Logging to a PV - int configurePvLogging(); + caPutJsonLogStatus configurePvLogging(); void logToPV(std::string &msg); // Logger helper methods void calculateMin(VALUE *pres, const VALUE *pa, const VALUE *pb, short type); void calculateMax(VALUE *pres, const VALUE *pa, const VALUE *pb, short type); bool compareValue(const VALUE *pa, const VALUE *pb, short type); - int fieldVal2Str(char *pbuf, size_t buflen, const VALUE *pval, short type, int index); + int fieldVal2Str(char *pbuf, size_t buflen, const VALUE *pval, short type, int index); }; From 3a4dba3135e0f59bcc3d2ff6bf818033d889533c Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Fri, 31 Jul 2020 16:09:40 +0200 Subject: [PATCH 03/27] Json ca put log doxygen --- caPutLogApp/caPutJsonLogTask.h | 176 ++++++++++++++++++++++++++++----- 1 file changed, 154 insertions(+), 22 deletions(-) diff --git a/caPutLogApp/caPutJsonLogTask.h b/caPutLogApp/caPutJsonLogTask.h index 8187d63..f2f0e79 100644 --- a/caPutLogApp/caPutJsonLogTask.h +++ b/caPutLogApp/caPutJsonLogTask.h @@ -27,7 +27,7 @@ #include #include -// This module imports +// Includes from this module #include "caPutLogTask.h" // Status return values @@ -47,35 +47,102 @@ enum caPutJsonLogConfig { #ifdef __cplusplus +/** + * @brief Implementation of EPICS Channel Access put logger. Log messages are produced + * in JSON format: + * \code{.txt} + * {"date": "
--"","time":"::","host":"","user":"","pv":"","new":,"old":""}\n + * \endcode + * Burst puts add extra JSON properties (supported only for numberic scalar puts): + * - "min": Which is a minimum value inside the burst period + * - "max": Represents maximum value inside the burst of puts + * Array puts add following JSON properties: + * - "new-size": new array length of array (in case of a lso/lsi record this is string length) + * - "old-size": old array length of array (in case of a lso/lsi record this is string length) + * + * Implementation registers trap inside the Access security trap via "caPutLogAs.h" interface. After each caput our + * callback method is called which add a `LOGDATA` structure to the queue. On the logger thread we take the messages and + * check fo the bursts (with timeout). When all required information is known we generate JSON messsage, which is then + * send to the logging server and PV if configured. + * + * This class is designed as a singleton object. + * + * For the instructions how to use the utility please refer to the user manual. + * + */ class CaPutJsonLogTask { public: - /* - * Data - */ - static const int default_port = 7011; + // Default port to be used if not specified by the user + static const int default_port = 7011; - /* - * Methods + /** + * @brief Get the singleton Instance object. + * + * @return CaPutJsonLogTask* Pointer to the only instance of the CaPutJsonLogTask object. */ - // Class methods static CaPutJsonLogTask* getInstance() noexcept; + + /** + * @brief Initialize the object. + * + * @param address IP address or hostname of the log server. Can include a port number after a colon, + * if port number is not specified, default value will be used. + * @param config Configuration paramteter. Valid value are -1 <= config <= 2. + * @return caPutJsonLogStatus Status code. + */ caPutJsonLogStatus initialize(const char* address, caPutJsonLogConfig config); + + /** + * @brief Add a put details packed as a ::LOGDATA structure to a queue for processing. + * This method registered as a callback inside the caPutLog.h. + * + * @param plogData ::LOGDATA structure holding details about caput. + */ void addPutToQueue(LOGDATA * plogData); + + /** + * @brief Main loop of the logger. This method is started in a separate thread. + * It takes a caput messages from a queue process them and calls buildJsonMsg(). + * + * @param arg Parameter is not used. Could be in the feature. + */ void caPutJsonLogTask(void *arg); //Must be public, called from C + /** + * @brief Start the logging. + * + * @return int Status code. + */ caPutJsonLogStatus start(); + + /** + * @brief Stop the logging. + * + * @return int Status code. + */ caPutJsonLogStatus stop(); - // Logger config methods + /** + * @brief Reconfigure the logging. + * + * @param config New configuration. Valid value are -1 <= config <= 2. Invalid + * value will default to 1 "caPutJsonLogAll". + * @return int Status code. + */ caPutJsonLogStatus reconfigure(caPutJsonLogConfig config); + + /** + * @brief Print report client logger information to the console. + * + * @param level Level of details to be printed. + * @return int Status code. + */ caPutJsonLogStatus report(int level); private: - /* - * Data - */ - // Class variables + + // Singelton instance of this class. static CaPutJsonLogTask *instance; // Logger configuration @@ -96,32 +163,97 @@ class CaPutJsonLogTask { DBADDR caPutJsonLogPV; DBADDR *pCaPutJsonLogPV; - /* - * Methods - */ + // Class methods (Do not allow public constructors - class is designed as singleton) CaPutJsonLogTask(); virtual ~CaPutJsonLogTask(); CaPutJsonLogTask(const CaPutJsonLogTask&); CaPutJsonLogTask(const CaPutJsonLogTask&&); - // Logger logic + /** + * @brief Build a JSON string from and call logToServer() and logToPV() methods to log a message. + * + * @param pold_value Pointer to a ::VALUE structure holding an old PV value. + * @param pLogData Pointer to a ::LOGDATA structure holding new value and other meta databa about the put. + * @param burst Boolean value. It determinate if put was a burst of values. + * @param pmin Pointer to a ::VALUE structure holding an min value if burst is true. + * @param pmax Pointer to a ::VALUE structure holding an max value if burst is true. + * @return int Status code. + */ caPutJsonLogStatus buildJsonMsg(const VALUE *pold_value, const LOGDATA *pLogData, - bool burst, const VALUE *pmin, const VALUE *pmax); + bool burst, const VALUE *pmin, const VALUE *pmax); - // Logging to a server + /** + * @brief Configure logging to a server. + * + * @param address IP address or hostname of the logging server. + * @return int Status code. + */ caPutJsonLogStatus configureServerLogging(const char* address); + + /** + * @brief This method will send a message to the configured log server. + * + * @param msg Message to be send. + */ void logToServer(std::string &msg); - // Logging to a PV + /** + * @brief Configure logging to a PV. + * + * @return int Status code. + */ caPutJsonLogStatus configurePvLogging(); + + /** + * @brief Method will write a message to the configured PV. + * + * @param msg Message to be written. + */ void logToPV(std::string &msg); - // Logger helper methods + /** + * @brief Calculate minimum value of two ::VALUE unions. + * + * @param pres ::VALUE structure where result should be written. + * @param pa ::VALUE structure of the first value to be compared. + * @param pb ::VALUE structure of the second value to be compared. + * @param type EPICS DRB_* type stored in the input structures. + */ void calculateMin(VALUE *pres, const VALUE *pa, const VALUE *pb, short type); + + /** + * @brief Calculate maximum value of two ::VALUE unions. + * + * @param pres ::VALUE structure where result should be written. + * @param pa ::VALUE structure of the first value to be compared. + * @param pb ::VALUE structure of the second value to be compared. + * @param type EPICS DRB_* type stored in the input structures. + */ void calculateMax(VALUE *pres, const VALUE *pa, const VALUE *pb, short type); + + /** + * @brief Compare values in two ::VALUE structures if they are the same. + * + * @param pa First ::VALUE structure to be compared. + * @param pb Second ::VALUE structure to be compared. + * @param type EPICS DRB_* type stored in the input structures. + * @return true If values are the same. + * @return false If values are not the same. + */ bool compareValue(const VALUE *pa, const VALUE *pb, short type); - int fieldVal2Str(char *pbuf, size_t buflen, const VALUE *pval, short type, int index); + + /** + * @brief Get a string representation of the value stored in the ::VALUE. + * + * @param pbuf Pointer to a character buffer where the output should be written. + * @param buflen Length of the `pbuf`. + * @param pval Pointer to the ::VALUE structure to be transformed to the string representation. + * @param type EPICS DRB_* type stored in the input structure. + * @param index Index of the element in case of an array. For scalar values this must be 0. + * @return int Status return code. + */ + int fieldVal2Str(char *pbuf, size_t buflen, const VALUE *pval, short type, int index); }; From 9a8aee1f8247e0e0de9b6a298de1787c3d42b7a5 Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Mon, 3 Aug 2020 11:00:21 +0200 Subject: [PATCH 04/27] Define JSON_AND_ARRAYS_SUPPORTED macro in Makefile --- caPutLogApp/Makefile | 1 + caPutLogApp/caPutLogTask.h | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/caPutLogApp/Makefile b/caPutLogApp/Makefile index bfaee0d..3b9f718 100644 --- a/caPutLogApp/Makefile +++ b/caPutLogApp/Makefile @@ -24,6 +24,7 @@ DBD += caPutLog.dbd # Add support for json format and arrays # This requires EPICS base version 7.0.1 or higher ifdef BASE_7_0 +USR_CPPFLAGS += -DJSON_AND_ARRAYS_SUPPORTED caPutLog_SRCS += caPutJsonLogTask.cpp caPutLog_SRCS += caPutJsonLogShellCommands.cpp INC += caPutJsonLogTask.h diff --git a/caPutLogApp/caPutLogTask.h b/caPutLogApp/caPutLogTask.h index 65eaf2d..b194f1c 100644 --- a/caPutLogApp/caPutLogTask.h +++ b/caPutLogApp/caPutLogTask.h @@ -13,8 +13,6 @@ extern "C" { #define MAX_USERID_SIZE 32 #define MAX_HOSTID_SIZE 32 -#define JSON_AND_ARRAYS_SUPPORTED (EPICS_VERSION * 10000 + EPICS_REVISION * 100 + EPICS_MODIFICATION >= 70001) - #if JSON_AND_ARRAYS_SUPPORTED #define MAX_ARRAY_SIZE_BYTES 400 #else From d8659d537f587ac7da17dc5905dde3a9a7e90d8e Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Mon, 3 Aug 2020 15:01:01 +0200 Subject: [PATCH 05/27] Switch from std atomic to epicsAtomic --- caPutLogApp/caPutJsonLogTask.cpp | 16 ++++++++-------- caPutLogApp/caPutJsonLogTask.h | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/caPutLogApp/caPutJsonLogTask.cpp b/caPutLogApp/caPutJsonLogTask.cpp index 4aeb41f..ea32128 100644 --- a/caPutLogApp/caPutJsonLogTask.cpp +++ b/caPutLogApp/caPutJsonLogTask.cpp @@ -24,10 +24,10 @@ #include #include #include +#include #include #include #include -// #include #include // This module imports @@ -73,9 +73,9 @@ caPutJsonLogStatus CaPutJsonLogTask::reconfigure(caPutJsonLogConfig config) if ((config < caPutJsonLogNone) || (config > caPutJsonLogAllNoFilter)) { errlogSevPrintf(errlogMinor, "caPutJsonLog: invalid config request, setting to default 'caPutJsonLogAll'\n"); - this->config = caPutJsonLogAll; + epics::atomic::set(this->config, caPutJsonLogAll); } else { - this->config = config; + epics::atomic::set(this->config, config); } return caPutJsonLogSuccess; } @@ -143,7 +143,7 @@ caPutJsonLogStatus CaPutJsonLogTask::start() } // Create logging thread - this->taskStopper = false; + epics::atomic::set(this->taskStopper, false); const char * threadName = "caPutJsonLog"; threadId = epicsThreadCreate(threadName, epicsThreadPriorityLow, @@ -160,7 +160,7 @@ caPutJsonLogStatus CaPutJsonLogTask::start() caPutJsonLogStatus CaPutJsonLogTask::stop() { // Send signal to stop the logger worker thread - this->taskStopper = true; + epics::atomic::set(this->taskStopper, true); // Deregister Access Security trap caPutLogAsStop(); @@ -234,7 +234,7 @@ void CaPutJsonLogTask::caPutJsonLogTask(void *arg) } // Main loop of the logger, which accepts the caput changes and process them - while (!this->taskStopper) + while (!(bool)epics::atomic::get(this->taskStopper)) { int msgSize; @@ -259,7 +259,7 @@ void CaPutJsonLogTask::caPutJsonLogTask(void *arg) // Previous and new PV are the same and we are applying the burst filter else if ((pnext->pfield == pcurrent->pfield) - && (this->config != caPutJsonLogAllNoFilter) + && (epics::atomic::get(this->config) != caPutJsonLogAllNoFilter) && (pcurrent->type != DBR_CHAR)) { // Free "old" value @@ -306,7 +306,7 @@ void CaPutJsonLogTask::caPutJsonLogTask(void *arg) burst = false; } } - this->taskStopper = false; + epics::atomic::set(this->taskStopper, false); errlogSevPrintf(errlogInfo, "caPutJsonLog: log task exiting\n"); } diff --git a/caPutLogApp/caPutJsonLogTask.h b/caPutLogApp/caPutJsonLogTask.h index f2f0e79..df553b1 100644 --- a/caPutLogApp/caPutJsonLogTask.h +++ b/caPutLogApp/caPutJsonLogTask.h @@ -16,7 +16,6 @@ // Standard library imports #include -#include // Epics base imports #include @@ -147,14 +146,14 @@ class CaPutJsonLogTask { // Logger configuration const char * address; - std::atomic_int config; + int config; // To modify or read this value only epicsAtomic methods should be used // Interthread communication epicsMessageQueue caPutJsonLogQ; // Working thread epicsThreadId threadId; - std::atomic_bool taskStopper; + int taskStopper; // To modify or read this value only epicsAtomic methods should be used //Logging to a server logClientId caPutJsonLogClient; From b5a8e866b446674a4bdde15b328752ac3a5a10c9 Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Mon, 3 Aug 2020 15:53:10 +0200 Subject: [PATCH 06/27] Handle better how array size is determinated --- caPutLogApp/caPutLogAs.c | 34 +++++++++++++++++----------------- caPutLogApp/caPutLogAs.h | 2 ++ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/caPutLogApp/caPutLogAs.c b/caPutLogApp/caPutLogAs.c index e16e2ed..963371b 100644 --- a/caPutLogApp/caPutLogAs.c +++ b/caPutLogApp/caPutLogAs.c @@ -121,9 +121,6 @@ static void caPutLogAs(asTrapWriteMessage *pmessage, int afterPut) LOGDATA *plogData; long options, num_elm; long status; - rset *prset; - long noElements = 0; - long offset = 0; if (!afterPut) { /* before put */ plogData = caPutLogDataCalloc(); @@ -155,13 +152,7 @@ static void caPutLogAs(asTrapWriteMessage *pmessage, int afterPut) status = dbGetField( paddr, plogData->type, &plogData->old_value, &options, &num_elm, 0); plogData->old_log_size = num_elm; - if ((prset = dbGetRset(paddr)) && - prset->get_array_info) { - prset->get_array_info(paddr, &noElements, &offset); - plogData->old_size = noElements; - } else { - plogData->old_size = num_elm; - } + plogData->old_size = caPutLogActualArraySize(paddr); if (status) { errlogPrintf("caPutLog: dbGetField error=%ld\n", status); @@ -179,13 +170,7 @@ static void caPutLogAs(asTrapWriteMessage *pmessage, int afterPut) status = dbGetField( paddr, plogData->type, &plogData->new_value, &options, &num_elm, 0); plogData->new_log_size = num_elm; - if ((prset = dbGetRset(paddr)) && - prset->get_array_info) { - prset->get_array_info(paddr, &noElements, &offset); - plogData->new_size = noElements; - } else { - plogData->new_size = num_elm; - } + plogData->new_size = caPutLogActualArraySize(paddr); if (status) { errlogPrintf("caPutLog: dbGetField error=%ld.\n", status); @@ -239,6 +224,21 @@ int caPutLogMaxArraySize(short type) #endif } +long caPutLogActualArraySize(dbAddr * paddr) +{ + rset *prset = dbGetRset(paddr); + long nActual; + long offset; + + if (paddr->no_elements > 1 && + prset->get_array_info) { + prset->get_array_info(paddr, &nActual, &offset); + } else { + nActual = paddr->no_elements; + } + return nActual; +} + void caPutLogDataFree(LOGDATA *plogData) { freeListFree(logDataFreeList, plogData); diff --git a/caPutLogApp/caPutLogAs.h b/caPutLogApp/caPutLogAs.h index 00d0d6a..f57a5c9 100644 --- a/caPutLogApp/caPutLogAs.h +++ b/caPutLogApp/caPutLogAs.h @@ -2,6 +2,7 @@ #define INCcaPutLogAsh 1 #include +#include #include "caPutLogTask.h" @@ -15,6 +16,7 @@ epicsShareFunc void caPutLogDataFree(LOGDATA *pLogData); epicsShareFunc LOGDATA* caPutLogDataCalloc(void); int caPutLogMaxArraySize(short type); +long caPutLogActualArraySize(dbAddr * paddr); #ifdef __cplusplus } From deac70395ab98d312eaea0a296523447bb71674c Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Mon, 3 Aug 2020 17:54:56 +0200 Subject: [PATCH 07/27] Handle arrays better --- caPutLogApp/caPutJsonLogTask.cpp | 52 +++++++++++--------------------- caPutLogApp/caPutLogAs.c | 50 ++++++++++++++---------------- caPutLogApp/caPutLogTask.h | 1 + 3 files changed, 40 insertions(+), 63 deletions(-) diff --git a/caPutLogApp/caPutJsonLogTask.cpp b/caPutLogApp/caPutJsonLogTask.cpp index ea32128..f74f6d1 100644 --- a/caPutLogApp/caPutJsonLogTask.cpp +++ b/caPutLogApp/caPutJsonLogTask.cpp @@ -228,7 +228,7 @@ void CaPutJsonLogTask::caPutJsonLogTask(void *arg) this->caPutJsonLogQ.receive(&pcurrent, sizeof(LOGDATA *)); std::memcpy(pold, &pcurrent->old_value, sizeof(VALUE)); - if (pcurrent->new_size > 0 && pcurrent->type != DBF_CHAR) { + if (pcurrent->new_size > 0 && pcurrent->type != DBR_CHAR) { std::memcpy(pmax, &pcurrent->new_value.value, sizeof(VALUE)); std::memcpy(pmin, &pcurrent->new_value.value, sizeof(VALUE)); } @@ -399,28 +399,23 @@ caPutJsonLogStatus CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const strlen(reinterpret_cast(str_newVal)))); // Open Json array if we have array value - if (pLogData->new_size > 1 && pLogData->type != DBR_CHAR) { + if (pLogData->is_array) { CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_array_open(handle)); } - // We have empty array - if (pLogData->new_size == 0) { - const unsigned char empty_value[] = ""; - CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, empty_value, - strlen(reinterpret_cast(empty_value)))); - } + // We have string - else if (pLogData->type == DBF_CHAR){ + if (pLogData->type == DBR_CHAR){ fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, - &pLogData->new_value.value, DBF_CHAR, 0); + &pLogData->new_value.value, DBR_CHAR, 0); CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, strlen(reinterpret_cast(interBuffer)))); } - // Arrays and scalars (all except DBF_CHAR) + // Arrays and scalars (all except DBR_CHAR) else { for (int i = 0; i < pLogData->new_log_size; i++) { fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, &pLogData->new_value.value, pLogData->type, i); - if (pLogData->type == DBF_STRING) { + if (pLogData->type == DBR_STRING) { CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, strlen(reinterpret_cast(interBuffer)))); } else { @@ -430,12 +425,9 @@ caPutJsonLogStatus CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const } } } - // Close Json array if we have array value - if (pLogData->new_size > 1 && pLogData->type != DBR_CHAR) { + // Close Json array and add new size, but only if we have array + if (pLogData->is_array) { CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_array_close(handle)); - } - // Add new size, but only if we have array - if (pLogData->new_size > 1) { const unsigned char str_newSize[] = "new-size"; CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_newSize, strlen(reinterpret_cast(str_newSize)))); @@ -447,28 +439,22 @@ caPutJsonLogStatus CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_oldVal, strlen(reinterpret_cast(str_oldVal)))); // Open Json array if we have array value - if (pLogData->old_size > 1 && pLogData->type != DBR_CHAR) { + if (pLogData->is_array) { CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_array_open(handle)); } - // We have empty array - if (pLogData->old_size == 0) { - const unsigned char empty_value[] = ""; - CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, empty_value, - strlen(reinterpret_cast(empty_value)))); - } // We have string - else if (pLogData->type == DBF_CHAR){ + if (pLogData->type == DBR_CHAR){ fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, - pold_value, DBF_CHAR, 0); + pold_value, DBR_CHAR, 0); CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, strlen(reinterpret_cast(interBuffer)))); } - // Arrays and scalars (all except DBF_CHAR) + // Arrays and scalars (all except DBR_CHAR) else { for (int i = 0; i < pLogData->old_log_size; i++) { fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, pold_value, pLogData->type, i); - if (pLogData->type == DBF_STRING) { + if (pLogData->type == DBR_STRING) { CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, strlen(reinterpret_cast(interBuffer)));); } else { @@ -478,12 +464,9 @@ caPutJsonLogStatus CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const } } } - // Close Json array if we have array value - if (pLogData->old_size > 1 && pLogData->type != DBR_CHAR) { + // Close Json array and add new size, but only if we have array + if (pLogData->is_array) { CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_array_close(handle)); - } - // Add new size, but only if we have array - if (pLogData->old_size > 1) { const unsigned char str_oldSize[] = "old-size"; CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_oldSize, strlen(reinterpret_cast(str_oldSize)))); @@ -492,8 +475,7 @@ caPutJsonLogStatus CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const // Add minium and maximum values in case of a burst if (burst && isDbrNumeric(pLogData->type) - && pLogData->old_size == 1 - && pLogData->new_size == 1) { + && !pLogData->is_array) { // Add min value const unsigned char str_minVal[] = "min"; CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_minVal, diff --git a/caPutLogApp/caPutLogAs.c b/caPutLogApp/caPutLogAs.c index 963371b..d8e9fa9 100644 --- a/caPutLogApp/caPutLogAs.c +++ b/caPutLogApp/caPutLogAs.c @@ -153,6 +153,7 @@ static void caPutLogAs(asTrapWriteMessage *pmessage, int afterPut) paddr, plogData->type, &plogData->old_value, &options, &num_elm, 0); plogData->old_log_size = num_elm; plogData->old_size = caPutLogActualArraySize(paddr); + plogData->is_array = paddr->no_elements > 1 ? TRUE : FALSE; if (status) { errlogPrintf("caPutLog: dbGetField error=%ld\n", status); @@ -171,6 +172,7 @@ static void caPutLogAs(asTrapWriteMessage *pmessage, int afterPut) paddr, plogData->type, &plogData->new_value, &options, &num_elm, 0); plogData->new_log_size = num_elm; plogData->new_size = caPutLogActualArraySize(paddr); + plogData->is_array = plogData->is_array || paddr->no_elements > 1 ? TRUE : FALSE; if (status) { errlogPrintf("caPutLog: dbGetField error=%ld.\n", status); @@ -189,37 +191,29 @@ static void caPutLogAs(asTrapWriteMessage *pmessage, int afterPut) int caPutLogMaxArraySize(short type) { - #if !JSON_AND_ARRAYS_SUPPORTED return 1; #else - switch (type) { - case DBR_CHAR: - return MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt8); - case DBR_UCHAR: - return MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt8); - case DBR_SHORT: - return MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt16); - case DBR_USHORT: - case DBR_ENUM: - return MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt16); - case DBR_LONG: - return MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt32); - case DBR_ULONG: - return MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt32); - #ifdef DBR_INT64 - case DBR_INT64: - return MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt64); - case DBR_UINT64: - return MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt64); - #endif - case DBR_FLOAT: - return MAX_ARRAY_SIZE_BYTES/sizeof(epicsFloat32); - case DBR_DOUBLE: - return MAX_ARRAY_SIZE_BYTES/sizeof(epicsFloat64); - case DBR_STRING: - return MAX_ARRAY_SIZE_BYTES/MAX_STRING_SIZE; - + static int const arraySizeLookUpTable [] = { + MAX_ARRAY_SIZE_BYTES/MAX_STRING_SIZE, /* DBR_STRING */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt8), /* DBR_CHAR */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt8), /* DBR_UCHAR */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt16), /* DBR_SHORT */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt16), /* DBR_USHORT */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt32), /* DBR_LONG */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt32), /* DBR_ULONG */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsInt64), /* DBR_INT64 */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt64), /* DBR_UINT64 */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsFloat32), /* DBR_FLOAT */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsFloat64), /* DBR_DOUBLE */ + MAX_ARRAY_SIZE_BYTES/sizeof(epicsUInt16) /* DBR_ENUM */ + }; + + if (type >= DBR_STRING || type <= DBR_ENUM){ + return arraySizeLookUpTable[type]; + } else { + errlogSevPrintf(errlogMajor, "caPutLogAs: Array size for type %d can not be determind\n", type); + return 1; } #endif } diff --git a/caPutLogApp/caPutLogTask.h b/caPutLogApp/caPutLogTask.h index b194f1c..6339b1d 100644 --- a/caPutLogApp/caPutLogTask.h +++ b/caPutLogApp/caPutLogTask.h @@ -60,6 +60,7 @@ typedef struct { TS_STAMP time; VALUE value; } new_value; + int is_array; int old_size; int old_log_size; int new_size; From 47a5a12593d0b36a99258ec7953232156551b956 Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Mon, 3 Aug 2020 18:06:43 +0200 Subject: [PATCH 08/27] Add share attribute to new class/functions --- caPutLogApp/caPutJsonLogTask.h | 8 ++++---- caPutLogApp/caPutLogAs.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/caPutLogApp/caPutJsonLogTask.h b/caPutLogApp/caPutJsonLogTask.h index df553b1..a52a853 100644 --- a/caPutLogApp/caPutJsonLogTask.h +++ b/caPutLogApp/caPutJsonLogTask.h @@ -69,7 +69,7 @@ enum caPutJsonLogConfig { * For the instructions how to use the utility please refer to the user manual. * */ -class CaPutJsonLogTask { +class epicsShareClass CaPutJsonLogTask { public: // Default port to be used if not specified by the user @@ -262,9 +262,9 @@ extern "C" { /* * C interface functions (Called from base) */ -void caddPutToQueue(LOGDATA * plogData); -void caPutJsonLogWorker(void *arg); -void caPutJsonLogExit(void *arg); +epicsShareFunc void caddPutToQueue(LOGDATA * plogData); +epicsShareFunc void caPutJsonLogWorker(void *arg); +epicsShareFunc void caPutJsonLogExit(void *arg); #ifdef __cplusplus } diff --git a/caPutLogApp/caPutLogAs.h b/caPutLogApp/caPutLogAs.h index f57a5c9..45f6d0e 100644 --- a/caPutLogApp/caPutLogAs.h +++ b/caPutLogApp/caPutLogAs.h @@ -15,8 +15,8 @@ epicsShareFunc void caPutLogAsStop(); epicsShareFunc void caPutLogDataFree(LOGDATA *pLogData); epicsShareFunc LOGDATA* caPutLogDataCalloc(void); -int caPutLogMaxArraySize(short type); -long caPutLogActualArraySize(dbAddr * paddr); +epicsShareFunc int caPutLogMaxArraySize(short type); +epicsShareFunc long caPutLogActualArraySize(dbAddr * paddr); #ifdef __cplusplus } From f4683afa4228782f2603c05d58bce7f15055a0cc Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Tue, 4 Aug 2020 08:41:43 +0200 Subject: [PATCH 09/27] Correctly handle new line at the end of the log First log to a PV so we can append a new line character after for logging to a server --- caPutLogApp/caPutJsonLogTask.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/caPutLogApp/caPutJsonLogTask.cpp b/caPutLogApp/caPutJsonLogTask.cpp index f74f6d1..bbb7f74 100644 --- a/caPutLogApp/caPutJsonLogTask.cpp +++ b/caPutLogApp/caPutJsonLogTask.cpp @@ -508,16 +508,16 @@ caPutJsonLogStatus CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const /* Get a JSON as a string */ std::string json (reinterpret_cast(buf)); - /* Log */ - this->logToServer(json); + /* First log to a PV so we can append new line later for the logging to a server */ this->logToPV(json); + this->logToServer(json.append("\n")); return caPutJsonLogSuccess; } void CaPutJsonLogTask::logToServer(std::string &msg) { if (caPutJsonLogClient) { - logClientSend(caPutJsonLogClient, msg.append("\n").c_str()); + logClientSend(caPutJsonLogClient, msg.c_str()); } } From 901b4beb68f87a9ac3747c07c68bccd01b82f6c5 Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Tue, 4 Aug 2020 10:59:35 +0200 Subject: [PATCH 10/27] Handle Nan and +/- infinity properly --- caPutLogApp/caPutJsonLogTask.cpp | 111 ++++++++++++++++++++++++++----- caPutLogApp/caPutJsonLogTask.h | 34 ++++++++++ 2 files changed, 127 insertions(+), 18 deletions(-) diff --git a/caPutLogApp/caPutJsonLogTask.cpp b/caPutLogApp/caPutJsonLogTask.cpp index bbb7f74..7b1fe88 100644 --- a/caPutLogApp/caPutJsonLogTask.cpp +++ b/caPutLogApp/caPutJsonLogTask.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -413,15 +414,32 @@ caPutJsonLogStatus CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const // Arrays and scalars (all except DBR_CHAR) else { for (int i = 0; i < pLogData->new_log_size; i++) { - fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, - &pLogData->new_value.value, pLogData->type, i); - if (pLogData->type == DBR_STRING) { - CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, - strlen(reinterpret_cast(interBuffer)))); - } else { - CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_number(handle, - reinterpret_cast(interBuffer), - strlen(reinterpret_cast(interBuffer)))); + if (this->isNan(&pLogData->new_value.value, pLogData->type, i)){ + const unsigned char str_Nan[] = "Nan"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_Nan, + strlen(reinterpret_cast(str_Nan)))); + } + else if (this->isPInfinity(&pLogData->new_value.value, pLogData->type, i)){ + const unsigned char str_pinf[] = "Infinity"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_pinf, + strlen(reinterpret_cast(str_pinf)))); + } + else if (this->isNInfinity(&pLogData->new_value.value, pLogData->type, i)){ + const unsigned char str_ninf[] = "-Infinity"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_ninf, + strlen(reinterpret_cast(str_ninf)))); + } + else { + fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, + &pLogData->new_value.value, pLogData->type, i); + if (pLogData->type == DBR_STRING) { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, + strlen(reinterpret_cast(interBuffer)))); + } else { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_number(handle, + reinterpret_cast(interBuffer), + strlen(reinterpret_cast(interBuffer)))); + } } } } @@ -452,15 +470,32 @@ caPutJsonLogStatus CaPutJsonLogTask::buildJsonMsg(const VALUE *pold_value, const // Arrays and scalars (all except DBR_CHAR) else { for (int i = 0; i < pLogData->old_log_size; i++) { - fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, - pold_value, pLogData->type, i); - if (pLogData->type == DBR_STRING) { - CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, - strlen(reinterpret_cast(interBuffer)));); - } else { - CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_number(handle, - reinterpret_cast(interBuffer), - strlen(reinterpret_cast(interBuffer)))); + if (this->isNan(pold_value, pLogData->type, i)){ + const unsigned char str_Nan[] = "Nan"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_Nan, + strlen(reinterpret_cast(str_Nan)))); + } + else if (this->isPInfinity(pold_value, pLogData->type, i)){ + const unsigned char str_pinf[] = "Infinity"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_pinf, + strlen(reinterpret_cast(str_pinf)))); + } + else if (this->isNInfinity(pold_value, pLogData->type, i)){ + const unsigned char str_ninf[] = "-Infinity"; + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, str_ninf, + strlen(reinterpret_cast(str_ninf)))); + } + else { + fieldVal2Str(reinterpret_cast(interBuffer), interBufferSize, + pold_value, pLogData->type, i); + if (pLogData->type == DBR_STRING) { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_string(handle, interBuffer, + strlen(reinterpret_cast(interBuffer)));); + } else { + CALL_YAJL_FUNCTION_AND_CHECK_STATUS(status, yajl_gen_number(handle, + reinterpret_cast(interBuffer), + strlen(reinterpret_cast(interBuffer)))); + } } } } @@ -674,6 +709,46 @@ int CaPutJsonLogTask::fieldVal2Str(char *pbuf, size_t buflen, const VALUE *pval, } +bool CaPutJsonLogTask::isNan(const VALUE *pval, short type, int index) { + + switch (type) { + case DBR_FLOAT: + if (isnan(((epicsFloat32 *)pval)[index])) return true; + break; + case DBR_DOUBLE: + if (isnan(((epicsFloat64 *)pval)[index])) return true; + break; + } + return false; +} + +bool CaPutJsonLogTask::isPInfinity(const VALUE *pval, short type, int index) { + + switch (type) { + case DBR_FLOAT: + if (((epicsFloat32 *)pval)[index] == epicsINF) return true; + break; + case DBR_DOUBLE: + if (((epicsFloat64 *)pval)[index] == epicsINF) return true; + break; + } + return false; +} + +bool CaPutJsonLogTask::isNInfinity(const VALUE *pval, short type, int index) { + + switch (type) { + case DBR_FLOAT: + if (((epicsFloat32 *)pval)[index] == -epicsINF) return true; + break; + case DBR_DOUBLE: + if (((epicsFloat64 *)pval)[index] == -epicsINF) return true; + break; + } + return false; +} + + /* Called from C */ void caddPutToQueue(LOGDATA * plogData) diff --git a/caPutLogApp/caPutJsonLogTask.h b/caPutLogApp/caPutJsonLogTask.h index a52a853..4cfc817 100644 --- a/caPutLogApp/caPutJsonLogTask.h +++ b/caPutLogApp/caPutJsonLogTask.h @@ -253,6 +253,40 @@ class epicsShareClass CaPutJsonLogTask { * @return int Status return code. */ int fieldVal2Str(char *pbuf, size_t buflen, const VALUE *pval, short type, int index); + + /** + * @brief Check if ::VALUE represents Nan (Not a number). + * + * @param pval ::VALUE to be checked. + * @param type EPICS DRB_* type stored in the input ::VALUE. + * @param index Index of the element in case of an array. For scalar values this must be 0. + * @return true If ::VALUE represents Nan. + * @return false If ::VALUE does not represents Nan. + */ + bool isNan(const VALUE *pval, short type, int index); + + /** + * @brief Check if ::VALUE represents positive infinity. + * + * @param pval ::VALUE to be checked. + * @param type EPICS DRB_* type stored in the input ::VALUE. + * @param index Index of the element in case of an array. For scalar values this must be 0. + * @return true If ::VALUE represents positive infinity. + * @return false If ::VALUE does not represents positive infinity. + */ + bool isPInfinity(const VALUE *pval, short type, int index); + + /** + * @brief Check if ::VALUE represents negative infinity. + * + * @param pval ::VALUE to be checked. + * @param type EPICS DRB_* type stored in the input ::VALUE. + * @param index Index of the element in case of an array. For scalar values this must be 0. + * @return true If ::VALUE represents negative infinity. + * @return false If ::VALUE does not represents negative infinity. + */ + bool isNInfinity(const VALUE *pval, short type, int index); + }; From cc1eb3a8faf49c39441a0aa2670eb452757a298b Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Tue, 4 Aug 2020 12:26:39 +0200 Subject: [PATCH 11/27] Add ability to costumize queue size via variable --- caPutLogApp/caPutJsonLog.dbd | 1 + caPutLogApp/caPutJsonLogShellCommands.cpp | 4 ++++ caPutLogApp/caPutJsonLogTask.cpp | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/caPutLogApp/caPutJsonLog.dbd b/caPutLogApp/caPutJsonLog.dbd index 28fc064..685335f 100644 --- a/caPutLogApp/caPutJsonLog.dbd +++ b/caPutLogApp/caPutJsonLog.dbd @@ -1 +1,2 @@ +variable(caPutLogJsonMsgQueueSize,int) registrar(caPutJsonLogRegister) diff --git a/caPutLogApp/caPutJsonLogShellCommands.cpp b/caPutLogApp/caPutJsonLogShellCommands.cpp index 26e7669..2cd7e1b 100644 --- a/caPutLogApp/caPutJsonLogShellCommands.cpp +++ b/caPutLogApp/caPutJsonLogShellCommands.cpp @@ -21,6 +21,8 @@ /* EPICS iocsh shell commands */ extern "C" { + extern int caPutLogJsonMsgQueueSize; + /* Initalisation */ int caPutJsonLogInit(const char * address, caPutJsonLogConfig config){ CaPutJsonLogTask *logger = CaPutJsonLogTask::getInstance(); @@ -88,4 +90,6 @@ extern "C" iocshRegister(&caPutJsonLogShowDef,caPutJsonLogShowCall); } epicsExportRegistrar(caPutJsonLogRegister); + + epicsExportAddress(int,caPutLogJsonMsgQueueSize); } diff --git a/caPutLogApp/caPutJsonLogTask.cpp b/caPutLogApp/caPutJsonLogTask.cpp index 7b1fe88..000710f 100644 --- a/caPutLogApp/caPutJsonLogTask.cpp +++ b/caPutLogApp/caPutJsonLogTask.cpp @@ -39,6 +39,7 @@ #define isDbrNumeric(type) ((type) > DBR_STRING && (type) <= DBR_ENUM) +int caPutLogJsonMsgQueueSize = 1000; static const ENV_PARAM EPICS_CA_JSON_PUT_LOG_ADDR = {epicsStrDup("EPICS_CA_JSON_PUT_LOG_ADDR"), epicsStrDup("")}; CaPutJsonLogTask * CaPutJsonLogTask::instance = NULL; @@ -59,7 +60,7 @@ CaPutJsonLogTask *CaPutJsonLogTask::getInstance() noexcept } CaPutJsonLogTask::CaPutJsonLogTask() - : caPutJsonLogQ(1000, sizeof(LOGDATA *)), + : caPutJsonLogQ(caPutLogJsonMsgQueueSize, sizeof(LOGDATA *)), threadId(NULL), taskStopper(false), caPutJsonLogClient(NULL), From c4fd437adad2cb9e890864dc5934434fbe585fb7 Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Thu, 6 Aug 2020 09:07:10 +0200 Subject: [PATCH 12/27] Check if server was specified --- caPutLogApp/caPutJsonLogTask.cpp | 12 +++++++++--- caPutLogApp/caPutLogClient.c | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/caPutLogApp/caPutJsonLogTask.cpp b/caPutLogApp/caPutJsonLogTask.cpp index 000710f..1ffcc8e 100644 --- a/caPutLogApp/caPutJsonLogTask.cpp +++ b/caPutLogApp/caPutJsonLogTask.cpp @@ -176,11 +176,17 @@ caPutJsonLogStatus CaPutJsonLogTask::configureServerLogging(const char* address) struct sockaddr_in saddr; // Parse the address - if (!address || !address[0]) + if (!address || !address[0]) { address = envGetConfigParamPtr(&EPICS_CA_JSON_PUT_LOG_ADDR); - status = aToIPAddr (address, this->default_port, &saddr); + } + if (address == NULL) { + errlogSevPrintf(errlogMajor, "caPutJsonLog: server address not specified\n"); + return caPutJsonLogError; + } + + status = aToIPAddr(address, this->default_port, &saddr); if (status < 0) { - errlogSevPrintf(errlogMinor, "caPutJsonLog: bad address or host name\n"); + errlogSevPrintf(errlogMajor, "caPutJsonLog: bad address or host name\n"); return caPutJsonLogError; } diff --git a/caPutLogApp/caPutLogClient.c b/caPutLogApp/caPutLogClient.c index 9b326eb..ec81ef2 100644 --- a/caPutLogApp/caPutLogClient.c +++ b/caPutLogApp/caPutLogClient.c @@ -75,6 +75,10 @@ int caPutLogClientInit (const char *addr_str) if (!addr_str || !addr_str[0]) { addr_str = envGetConfigParamPtr(&EPICS_CA_PUT_LOG_ADDR); } + if (addr_str == NULL) { + errlogSevPrintf(errlogMajor, "caPutLog: server address not specified\n"); + return caPutLogError; + } status = aToIPAddr (addr_str, default_port, &saddr); if (status<0) { From ed30167d15d1d72cbafecb1faf4673c08b1988a7 Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Mon, 10 Aug 2020 09:48:20 +0200 Subject: [PATCH 13/27] Remove some c++11 features --- caPutLogApp/caPutJsonLogTask.cpp | 2 +- caPutLogApp/caPutJsonLogTask.h | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/caPutLogApp/caPutJsonLogTask.cpp b/caPutLogApp/caPutJsonLogTask.cpp index 1ffcc8e..49a9845 100644 --- a/caPutLogApp/caPutJsonLogTask.cpp +++ b/caPutLogApp/caPutJsonLogTask.cpp @@ -45,7 +45,7 @@ static const ENV_PARAM EPICS_CA_JSON_PUT_LOG_ADDR = {epicsStrDup("EPICS_CA_JSON_ CaPutJsonLogTask * CaPutJsonLogTask::instance = NULL; -CaPutJsonLogTask *CaPutJsonLogTask::getInstance() noexcept +CaPutJsonLogTask *CaPutJsonLogTask::getInstance() { if (instance == NULL) { try{ diff --git a/caPutLogApp/caPutJsonLogTask.h b/caPutLogApp/caPutJsonLogTask.h index 4cfc817..9e1099e 100644 --- a/caPutLogApp/caPutJsonLogTask.h +++ b/caPutLogApp/caPutJsonLogTask.h @@ -80,7 +80,7 @@ class epicsShareClass CaPutJsonLogTask { * * @return CaPutJsonLogTask* Pointer to the only instance of the CaPutJsonLogTask object. */ - static CaPutJsonLogTask* getInstance() noexcept; + static CaPutJsonLogTask* getInstance(); /** * @brief Initialize the object. @@ -167,7 +167,9 @@ class epicsShareClass CaPutJsonLogTask { CaPutJsonLogTask(); virtual ~CaPutJsonLogTask(); CaPutJsonLogTask(const CaPutJsonLogTask&); - CaPutJsonLogTask(const CaPutJsonLogTask&&); + + // Commeted as move constructor is c++11 feature, but we want compile on older versions as well + // CaPutJsonLogTask(const CaPutJsonLogTask&&); /** * @brief Build a JSON string from and call logToServer() and logToPV() methods to log a message. From 53579a7bf8be5b74fdec7d098e69084f20b73790 Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Mon, 10 Aug 2020 09:58:06 +0200 Subject: [PATCH 14/27] Replace nullptr with NULL as some targets may still not support it --- caPutLogApp/caPutJsonLogShellCommands.cpp | 6 +++--- caPutLogApp/caPutJsonLogTask.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/caPutLogApp/caPutJsonLogShellCommands.cpp b/caPutLogApp/caPutJsonLogShellCommands.cpp index 2cd7e1b..4898bb4 100644 --- a/caPutLogApp/caPutJsonLogShellCommands.cpp +++ b/caPutLogApp/caPutJsonLogShellCommands.cpp @@ -26,7 +26,7 @@ extern "C" /* Initalisation */ int caPutJsonLogInit(const char * address, caPutJsonLogConfig config){ CaPutJsonLogTask *logger = CaPutJsonLogTask::getInstance(); - if (logger != nullptr) return logger->initialize(address, config); + if (logger != NULL) return logger->initialize(address, config); else return -1; } @@ -46,7 +46,7 @@ extern "C" /* Reconfigure */ int caPutJsonLogReconf(caPutJsonLogConfig config){ CaPutJsonLogTask *logger = CaPutJsonLogTask::getInstance(); - if (logger != nullptr) return logger->reconfigure(config); + if (logger != NULL) return logger->reconfigure(config); else return -1; } @@ -63,7 +63,7 @@ extern "C" /* Report */ int caPutJsonLogShow(int level){ CaPutJsonLogTask *logger = CaPutJsonLogTask::getInstance(); - if (logger != nullptr) return logger->report(level); + if (logger != NULL) return logger->report(level); else return -1; } diff --git a/caPutLogApp/caPutJsonLogTask.cpp b/caPutLogApp/caPutJsonLogTask.cpp index 49a9845..d15f743 100644 --- a/caPutLogApp/caPutJsonLogTask.cpp +++ b/caPutLogApp/caPutJsonLogTask.cpp @@ -53,7 +53,7 @@ CaPutJsonLogTask *CaPutJsonLogTask::getInstance() } catch (...) { errlogSevPrintf(errlogMajor, "caPutJsonLog: Failed to construct CA put JSON logger\n"); - return nullptr; + return NULL; } } return instance; From feda03a66593f8a030ec5bc9194fe6f259103124 Mon Sep 17 00:00:00 2001 From: Matic Pogacnik Date: Wed, 5 Aug 2020 14:48:04 +0200 Subject: [PATCH 15/27] Init version of json docs --- docs/index.rst | 216 ++++++++++++++++++++++++++++++++---------- docs/releasenotes.rst | 8 ++ 2 files changed, 174 insertions(+), 50 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index f9c5b93..189d0ba 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,38 +16,51 @@ same steps as for the regular iocLogServer, except that you probably want to use a different port. On the IOC side there are three routines to be called from the iocShell. See section `Setup`_ below for details. +This module will send logs of CA puts in a text format to a remote logging +server. Currently, supports two output formats, ``standard`` and ``JSON``. +The Json format is only supported with `EPICS`_ base versions 7.0.1+. + Download -------- -You can download a release from the links in the table below, or get the -latest development version via `darcs`_:: +You can download a release from the `Github releases`_:: page or get the +latest development version via `Github`_:: - darcs get http://www-csr.bessy.de/control/SoftDist/caPutLog/repo/caPutLog + git clone https://github.com/epics-modules/caPutLog.git You can also `browse`_ through the latest changes in the repo. -+---------+---------------+-----------------------------------+---------------+ -| Version | EPICS Release | Filename | Release Notes | -+=========+===============+===================================+===============+ -| 3.5 | 3.14.12..3.15 | :download:`caPutLog-3.5.tar.gz` | :ref:`R3-5` | -+---------+---------------+-----------------------------------+---------------+ -| 3.4 | 3.14.12 | :download:`caPutLog-3.4.tar.gz` | :ref:`R3-4` | -+---------+---------------+-----------------------------------+---------------+ -| 3.3.3 | 3.14.12 | :download:`caPutLog-3.3.3.tar.gz` | :ref:`R3-3-3` | -+---------+---------------+-----------------------------------+---------------+ -| 3.3.2 | 3.14.12 | :download:`caPutLog-3.3.2.tar.gz` | :ref:`R3-3-2` | -+---------+---------------+-----------------------------------+---------------+ -| 3.3.1 | 3.14.12 | :download:`caPutLog-3.3.1.tar.gz` | :ref:`R3-3-1` | -+---------+---------------+-----------------------------------+---------------+ -| 3.3 | 3.14.12 | :download:`caPutLog-3.3.tar.gz` | :ref:`R3-3` | -+---------+---------------+-----------------------------------+---------------+ -| 3.2 | 3.14.11 | :download:`caPutLog-3.2.tar.gz` | :ref:`R3-2` | -+---------+---------------+-----------------------------------+---------------+ -| 3.1 | 3.14.8.2 | :download:`caPutLog-3.1.tar.gz` | :ref:`R3-1` | -+---------+---------------+-----------------------------------+---------------+ -| 3.0 | 3.14.8.2 | :download:`caPutLog-3.0.tar.gz` | n/a | -+---------+---------------+-----------------------------------+---------------+ +Compatibility matrix and release notes +++++++++++++++++++++++++++++++++++++++ + ++---------+------------------+------------------+ +| Version | EPICS Release | Release Notes | ++=========+==================+==================+ +| 4.0 | 3.14.12..7 | :ref:`R4-0` | ++---------+------------------+------------------+ +| 3.7 | | | ++---------+------------------+------------------+ +| 3.6 | | | ++---------+------------------+------------------+ +| 3.5 | 3.14.12..3.15 | :ref:`R3-5` | ++---------+------------------+------------------+ +| 3.4 | 3.14.12 | :ref:`R3-4` | ++---------+------------------+------------------+ +| 3.3.3 | 3.14.12 | :ref:`R3-3-3` | ++---------+------------------+------------------+ +| 3.3.2 | 3.14.12 | :ref:`R3-3-2` | ++---------+------------------+------------------+ +| 3.3.1 | 3.14.12 | :ref:`R3-3-1` | ++---------+------------------+------------------+ +| 3.3 | 3.14.12 | :ref:`R3-3` | ++---------+------------------+------------------+ +| 3.2 | 3.14.11 | :ref:`R3-2` | ++---------+------------------+------------------+ +| 3.1 | 3.14.8.2 | :ref:`R3-1` | ++---------+------------------+------------------+ +| 3.0 | 3.14.8.2 | | ++---------+------------------+------------------+ Setup @@ -57,29 +70,48 @@ Build +++++ Change the definition of ``EPICS_BASE`` in ``configure/RELEASE`` according to -the location of epics base on the host, then (gnu-)make. Add the install -directory to your IOC application's ``configure/RELEASE``, and in the -Makefile add ``caPutLog.dbd`` to your dbd includes and ``caPutLog`` to the -libraries to link. +the location of epics base on the host, then (gnu-)make. + +Include to your EPICS application ++++++++++++++++++++++++++++++++++ + +To include the module to your IOC application, add the install directory to your +application's ``configure/RELEASE``, and include ``dbd`` and ``lib`` in the +``src`` Makefile: :: + + _DBD += caPutLog.dbd # For standard format + _DBD += caPutJsonLog.dbd # For JSON format (Exists only if module is compiled with supported version of base) + _LIBS += caPutLog # Required for both output formats + +for libraries to link. Configure +++++++++ -In your IOC startup file add the command:: +.. note:: Only one instance of the logger is supported to run at the time. + +In your IOC startup file add the following command for the standard output format:: caPutLogInit "host[:port]" [config] +or for JSON output format:: + + caPutJsonLogInit "host[:port]" [config] + where ``host`` (mandatory argument) is the IP address or host name of the log -server and ``port`` is optional (the default is 7011). The environment -variable ``EPICS_CA_PUT_LOG_ADDR`` is used if the first parameter to -``caPutLogInit`` is ``NULL`` or the empty string. +server and ``port`` is optional (the default is 7011). + +The environment variable ``EPICS_CA_PUT_LOG_ADDR`` / ``EPICS_CA_PUT_JSON_LOG_ADDR`` +is used if the first parameter to ``caPutLogInit`` / ``caPutJsonLogInit`` is ``NULL`` +or the empty string, respectively. + +The second (optional, default=0) argument should be one of: -The second (optional, default=0) argument should be one of :: +- ``-1`` - No logging (disabled) +- ``0`` - Log only on value change (ignore if old and new values are equal) +- ``1`` - Log all puts with burst filter +- ``2`` - Log all puts without any filters - #define caPutLogNone -1 /* no logging (disable) */ - #define caPutLogOnChange 0 /* log only on value change */ - #define caPutLogAll 1 /* log all puts */ - #define caPutLogAllNoFilter 2 /* log all puts no filtering on same PV*/ Make sure access security is enabled on the IOC by providing a suitable configuration file and load it with a call to @@ -93,17 +125,16 @@ unrestricted access):: RULE(1,WRITE,TRAPWRITE) } +.. note:: ``caPutLogInit`` or ``caPutJsonLogInit`` are expecting access security + to be already running, so they must be called *after* iocInit. -Note that ``caPutLogInit`` expects access security to be already running, so -must be called *after* iocInit. +Other shell commands for logger are: -Other shell commands are: - -``caPutLogReconf config`` +``caPutLogReconf config`` / ``caPutJsonLogReconf config`` Change configuration on-line. The argument is the same as in - ``caPutLogInit``. + ``caPutLogInit`` / ``caPutJsonLogInit``. -``caPutLogShow level`` +``caPutLogShow level`` / ``caPutJsonLogShow level`` Show information about a running caPutLog, level is the usual interest level (0, 1, or 2). @@ -116,8 +147,8 @@ though. However, you can also use the same log server instance (so that caput log messages and regular IOC log messages go into the same log file). -Log Format ----------- +Standard Log Format ++++++++++++++++++++ The iocLogServer precedes each line with these data:: @@ -140,12 +171,93 @@ or :: The latter format means that several puts for the same PV have been received in rapid succession; in this case only the original and the final value as well as the minimum and maximum value are logged. This filtering can be -disabled by specifying the ``caPutLogAllNoFilter`` configuration option. +disabled by specifying the ``caPutLogAllNoFilter`` (``2``) configuration option. + + +Json Log Format ++++++++++++++++ + +``caPutJsonLogger`` is using Json as the output format. General format looks like :: + + {"date":"","time":"