From ec741563998fad078992ab45c021f4d270c5f661 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Thu, 14 Mar 2024 09:29:11 +0100 Subject: [PATCH] Fix issue with compress record The handling of N-to-M array compression was broken with the addition of the partial buffer option, which broke the bounds check that was being used. Note that this also makes the partial buffer option more consistent; if, for example, you have ``` record(compress, foo) { field(ALG, "N to 1 Average") field(INP, "bar NPP") field(NSAM, 2) field(N, 2) field(PBUF, YES) } ``` (with `bar` having, e.g. length 3), then this will now behave as expected on both of the samples. --- modules/database/src/std/rec/compressRecord.c | 80 +++++++++---------- modules/database/test/std/rec/compressTest.c | 66 ++++++++++++++- 2 files changed, 103 insertions(+), 43 deletions(-) diff --git a/modules/database/src/std/rec/compressRecord.c b/modules/database/src/std/rec/compressRecord.c index b6fafb4ab3..22e8049158 100644 --- a/modules/database/src/std/rec/compressRecord.c +++ b/modules/database/src/std/rec/compressRecord.c @@ -149,13 +149,16 @@ static int compare(const void *arg1, const void *arg2) else return 1; } +#define min(a, b) ((a) < (b) ? (a) : (b)) + static int compress_array(compressRecord *prec, double *psource, int no_elements) { - epicsInt32 i,j; + epicsInt32 j; epicsInt32 n, nnew; epicsInt32 nsam = prec->nsam; - double value; + epicsUInt32 samples_written = 0; + double value = 0.0; /* skip out of limit data */ if (prec->ilil < prec->ihil) { @@ -167,61 +170,54 @@ static int compress_array(compressRecord *prec, } if (prec->n <= 0) prec->n = 1; - if (no_elements < prec->n && prec->pbuf != menuYesNoYES) - return 1; /*dont do anything*/ - n = no_elements; + n = prec->n; - /* determine number of samples to take */ - if (no_elements < nsam * n) - nnew = (no_elements / n); - else nnew = nsam; + nnew = min(no_elements, nsam * n); - /* compress according to specified algorithm */ - switch (prec->alg){ - case compressALG_N_to_1_Low_Value: - /* compress N to 1 keeping the lowest value */ - for (i = 0; i < nnew; i++) { + while (nnew > 0) + { + if (nnew < n && prec->pbuf != menuYesNoYES) + break; + + n = min(n, nnew); + switch (prec->alg) + { + case compressALG_N_to_1_Low_Value: value = *psource++; - for (j = 1; j < n; j++, psource++) { + for (j = 1; j < n; j++, psource++) + { if (value > *psource) value = *psource; } - put_value(prec, &value, 1); - } - break; - case compressALG_N_to_1_High_Value: - /* compress N to 1 keeping the highest value */ - for (i = 0; i < nnew; i++){ + break; + case compressALG_N_to_1_High_Value: value = *psource++; - for (j = 1; j < n; j++, psource++) { + for (j = 1; j < n; j++, psource++) + { if (value < *psource) value = *psource; } - put_value(prec, &value, 1); - } - break; - case compressALG_N_to_1_Average: - /* compress N to 1 keeping the average value */ - for (i = 0; i < nnew; i++) { - value = 0; - for (j = 0; j < n; j++, psource++) + break; + case compressALG_N_to_1_Average: + value = *psource++; + for (j = 1; j < n; j++, psource++) + { value += *psource; - value /= n; - put_value(prec, &value, 1); - } - break; - - case compressALG_N_to_1_Median: - /* compress N to 1 keeping the median value */ - /* note: sorts source array (OK; it's a work pointer) */ - for (i = 0; i < nnew; i++, psource += nnew) { + } + value = value / n; + break; + case compressALG_N_to_1_Median: + /* note: sorts source array (OK; it's a work pointer) */ qsort(psource, n, sizeof(double), compare); value = psource[n / 2]; - put_value(prec, &value, 1); + psource += n; + break; } - break; + nnew -= n; + put_value(prec, &value, 1); + samples_written++; } - return 0; + return (samples_written == 0); } static int array_average(compressRecord *prec, diff --git a/modules/database/test/std/rec/compressTest.c b/modules/database/test/std/rec/compressTest.c index ed5f31e638..cedb28f485 100644 --- a/modules/database/test/std/rec/compressTest.c +++ b/modules/database/test/std/rec/compressTest.c @@ -469,6 +469,38 @@ testNto1Average(void) { testdbCleanup(); } +void testNto2Average(void) { + DBADDR wfaddr, caddr; + + testDiag("Test N to 1 Average, NSAM=2, N=2"); + + testdbPrepare(); + + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=2,N=2"); + + eltc(0); + testIocInitOk(); + eltc(1); + + fetchRecordOrDie("wf", wfaddr); + fetchRecordOrDie("comp", caddr); + + writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + + checkArrD("comp", 2, 1.5, 3.5, 0, 0); + dbScanUnlock(caddr.precord); + + testIocShutdownOk(); + testdbCleanup(); +} + void testNto1AveragePartial(void) { double buf = 0.0; @@ -517,6 +549,36 @@ testNto1AveragePartial(void) { testdbCleanup(); } +void +testNtoMPartial(void) { + DBADDR wfaddr, caddr; + + testDiag("Test Average, N to M, Partial"); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=2,N=3,PBUF=YES"); + + eltc(0); + testIocInitOk(); + eltc(1); + + fetchRecordOrDie("wf", wfaddr); + fetchRecordOrDie("comp", caddr); + + writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + + checkArrD("comp", 2, 2.0, 4.0, 0, 0); + dbScanUnlock(caddr.precord); + + testIocShutdownOk(); + testdbCleanup(); +} + void testNto1LowValue(void) { double buf = 0.0; @@ -634,12 +696,14 @@ testAIAveragePartial(void) { MAIN(compressTest) { - testPlan(132); + testPlan(134); testFIFOCirc(); testLIFOCirc(); testArrayAverage(); testNto1Average(); + testNto2Average(); testNto1AveragePartial(); + testNtoMPartial(); testAIAveragePartial(); testNto1LowValue(); return testDone();