Merge ~anj/epics-base/+git/base-3.15:decimate-filter into ~epics-core/epics-base/+git/epics-base:3.15

Proposed by Andrew Johnson
Status: Merged
Approved by: Andrew Johnson
Approved revision: 84c86e67e817e17024cc9fff151a8bfbd61cc89e
Merged at revision: 9722e707fd507efac23f68dc829936e24c25d000
Proposed branch: ~anj/epics-base/+git/base-3.15:decimate-filter
Merge into: ~epics-core/epics-base/+git/epics-base:3.15
Diff against target: 1092 lines (+651/-68)
12 files modified
documentation/RELEASE_NOTES.html (+17/-0)
src/ioc/db/dbEvent.c (+5/-0)
src/ioc/db/dbEvent.h (+1/-1)
src/std/filters/Makefile (+1/-0)
src/std/filters/decimate.c (+117/-0)
src/std/filters/filters.dbd.pod (+40/-0)
src/std/filters/sync.c (+16/-8)
src/std/filters/test/Makefile (+6/-0)
src/std/filters/test/dbndTest.c (+30/-1)
src/std/filters/test/decTest.c (+289/-0)
src/std/filters/test/epicsRunFilterTests.c (+2/-0)
src/std/filters/test/syncTest.c (+127/-58)
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
mdavidsaver Approve
Review via email: mp+368347@code.launchpad.net

Description of the change

Add the decimation channel filter that Hinko and Emanuele were looking for to the 3.15 branch.

This merge request contains the filter code, user documentation in the filters.dbd.pod file and Release Notes, and test code.

To post a comment you must log in.
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

I think the dropped db_field_log are being leaked. See comments inline.

review: Needs Fixing
Revision history for this message
Andrew Johnson (anj) wrote :

RL: Ask, if a filter function drops an event it must call db_delete_field_log().

Revision history for this message
Andrew Johnson (anj) wrote :

Revisit tests, MAD and RL to look further.
AI on AJ to fix the leak.

Revision history for this message
Andrew Johnson (anj) wrote :

Tonight's commit fixes the decimate filter and adds tests to ensure the field-logs get freed properly when dropped by the filter.

Work still to come on fixing the other filters and tests.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

It looks like the leak is plugged. valgrind turns up one (I think minor) issue.

> Conditional jump or move depends on uninitialised value(s)
> at 0x4C31CC2: __memcmp_sse4_1 (vg_replace_strmem.c:1094)
> by 0x10A27E: fl_equal (decTest.c:36)
> by 0x10A27E: checkAndOpenChannel.isra.0 (decTest.c:110)
> by 0x10985F: main (decTest.c:167)
>
> Conditional jump or move depends on uninitialised value(s)
> at 0x4C31CD6: __memcmp_sse4_1 (vg_replace_strmem.c:1094)
> by 0x10A27E: fl_equal (decTest.c:36)
> by 0x10A27E: checkAndOpenChannel.isra.0 (decTest.c:110)
> by 0x10985F: main (decTest.c:167)

Revision history for this message
Andrew Johnson (anj) wrote :

I tried adding code to set the pfl->field_size member in fl_setup(), but that wasn't enough to fix the valgrind reports and was also missing from the syncTest.c and dbndTest.c programs. I therefor added an initial memset(), which did fix them.

Last night I also committed some additional tests to those 3 programs which check for the expected number of items on the freelist, and I also fixed some leaks in the sync filter.

Revision history for this message
Andrew Johnson (anj) wrote :

Core Mtg: Suggest splitting off the bug-fixes to their own branch and commit separately.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

I haven't rerun valgrind myself, but I think it is good to go as the leak is plugged.

review: Approve
Revision history for this message
Andrew Johnson (anj) wrote :

Group 10/4: No issues.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html
2index 8ed4e04..c7fbc02 100644
3--- a/documentation/RELEASE_NOTES.html
4+++ b/documentation/RELEASE_NOTES.html
5@@ -16,6 +16,23 @@
6
7 <!-- Insert new items immediately below here ... -->
8
9+<h3>Added new decimation channel filter</h3>
10+
11+<p>A new server-side filter has been added to the IOC for reducing the number
12+and frequency of monitor updates from a channel by a client-specified factor.
13+The filter's behaviour is quite simplistic, it passes the first monitor event it
14+sees to the client and then drops the next N-1 events before passing another
15+event. For example to sample a 60Hz channel at 1Hz, a 10Hz channel every 6
16+seconds, or a 1Hz channel once every minute:</p>
17+
18+<pre> Hal$ camonitor 'test:channel.{"dec":{"n":60}}'
19+ ...</pre>
20+
21+<p>More information is included in the
22+<a href="filters.html">filters</a><!-- href for the EPICS website -->
23+<a href="../html/filters.html">documentation</a><!-- href for install tree -->
24+file.</p>
25+
26 <h3>Cleaning up with Multiple CA contexts in a Process</h3>
27
28 <p>Bruno Martins reported a problem with the CA client library at shutdown in a
29diff --git a/src/ioc/db/dbEvent.c b/src/ioc/db/dbEvent.c
30index fb1f3a1..d1f9548 100644
31--- a/src/ioc/db/dbEvent.c
32+++ b/src/ioc/db/dbEvent.c
33@@ -1165,3 +1165,8 @@ void db_delete_field_log (db_field_log *pfl)
34 freeListFree(dbevFieldLogFreeList, pfl);
35 }
36 }
37+
38+int db_available_logs(void)
39+{
40+ return (int) freeListItemsAvail(dbevFieldLogFreeList);
41+}
42diff --git a/src/ioc/db/dbEvent.h b/src/ioc/db/dbEvent.h
43index fe0e52f..2e496a7 100644
44--- a/src/ioc/db/dbEvent.h
45+++ b/src/ioc/db/dbEvent.h
46@@ -78,6 +78,7 @@ epicsShareFunc void db_event_disable (dbEventSubscription es);
47 epicsShareFunc struct db_field_log* db_create_event_log (struct evSubscrip *pevent);
48 epicsShareFunc struct db_field_log* db_create_read_log (struct dbChannel *chan);
49 epicsShareFunc void db_delete_field_log (struct db_field_log *pfl);
50+epicsShareFunc int db_available_logs(void);
51
52 #define DB_EVENT_OK 0
53 #define DB_EVENT_ERROR (-1)
54@@ -87,4 +88,3 @@ epicsShareFunc void db_delete_field_log (struct db_field_log *pfl);
55 #endif
56
57 #endif /*INCLdbEventh*/
58-
59diff --git a/src/std/filters/Makefile b/src/std/filters/Makefile
60index d453989..3a27361 100644
61--- a/src/std/filters/Makefile
62+++ b/src/std/filters/Makefile
63@@ -15,6 +15,7 @@ dbRecStd_SRCS += ts.c
64 dbRecStd_SRCS += dbnd.c
65 dbRecStd_SRCS += arr.c
66 dbRecStd_SRCS += sync.c
67+dbRecStd_SRCS += decimate.c
68
69 HTMLS += filters.html
70
71diff --git a/src/std/filters/decimate.c b/src/std/filters/decimate.c
72new file mode 100644
73index 0000000..502422f
74--- /dev/null
75+++ b/src/std/filters/decimate.c
76@@ -0,0 +1,117 @@
77+/*************************************************************************\
78+* Copyright (c) 2019 UChicago Argonne LLC, as Operator of Argonne
79+* National Laboratory.
80+* Copyright (c) 2010 Brookhaven National Laboratory.
81+* Copyright (c) 2010 Helmholtz-Zentrum Berlin
82+* fuer Materialien und Energie GmbH.
83+* EPICS BASE is distributed subject to a Software License Agreement found
84+* in file LICENSE that is included with this distribution.
85+\*************************************************************************/
86+
87+/*
88+ * Authors: Ralph Lange <Ralph.Lange@bessy.de>,
89+ * Andrew Johnson <anj@anl.gov>
90+ */
91+
92+#include <stdio.h>
93+
94+#include "freeList.h"
95+#include "db_field_log.h"
96+#include "chfPlugin.h"
97+#include "epicsExport.h"
98+
99+typedef struct myStruct {
100+ epicsInt32 n, i;
101+} myStruct;
102+
103+static void *myStructFreeList;
104+
105+static const
106+chfPluginArgDef opts[] = {
107+ chfInt32(myStruct, n, "n", 1, 0),
108+ chfPluginArgEnd
109+};
110+
111+static void * allocPvt(void)
112+{
113+ myStruct *my = (myStruct*) freeListCalloc(myStructFreeList);
114+ return (void *) my;
115+}
116+
117+static void freePvt(void *pvt)
118+{
119+ freeListFree(myStructFreeList, pvt);
120+}
121+
122+static int parse_ok(void *pvt)
123+{
124+ myStruct *my = (myStruct*) pvt;
125+
126+ if (my->n < 1)
127+ return -1;
128+
129+ return 0;
130+}
131+
132+static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
133+ db_field_log *passfl = NULL;
134+ myStruct *my = (myStruct*) pvt;
135+ epicsInt32 i = my->i;
136+
137+ if (pfl->ctx == dbfl_context_read)
138+ return pfl;
139+
140+ if (i++ == 0)
141+ passfl = pfl;
142+ else
143+ db_delete_field_log(pfl);
144+
145+ if (i >= my->n)
146+ i = 0;
147+
148+ my->i = i;
149+ return passfl;
150+}
151+
152+static void channelRegisterPre(dbChannel *chan, void *pvt,
153+ chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
154+{
155+ *cb_out = filter;
156+ *arg_out = pvt;
157+}
158+
159+static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent)
160+{
161+ myStruct *my = (myStruct*) pvt;
162+ printf("%*sDecimate (dec): n=%d, i=%d\n", indent, "",
163+ my->n, my->i);
164+}
165+
166+static chfPluginIf pif = {
167+ allocPvt,
168+ freePvt,
169+
170+ NULL, /* parse_error, */
171+ parse_ok,
172+
173+ NULL, /* channel_open, */
174+ channelRegisterPre,
175+ NULL, /* channelRegisterPost, */
176+ channel_report,
177+ NULL /* channel_close */
178+};
179+
180+static void decInitialize(void)
181+{
182+ static int firstTime = 1;
183+
184+ if (!firstTime) return;
185+ firstTime = 0;
186+
187+ if (!myStructFreeList)
188+ freeListInitPvt(&myStructFreeList, sizeof(myStruct), 64);
189+
190+ chfPluginRegister("dec", &pif, opts);
191+}
192+
193+epicsExportRegistrar(decInitialize);
194diff --git a/src/std/filters/filters.dbd.pod b/src/std/filters/filters.dbd.pod
195index d7ab785..27e356f 100644
196--- a/src/std/filters/filters.dbd.pod
197+++ b/src/std/filters/filters.dbd.pod
198@@ -14,6 +14,8 @@ The following filters are available in this release:
199
200 =item * L<Synchronize|/"Synchronize Filter sync">
201
202+=item * L<Decimation|/"Decimation Filter dec">
203+
204 =back
205
206 =head2 Using Filters
207@@ -245,3 +247,41 @@ periods only when "blue" is true by using
208 ...
209
210 =cut
211+
212+registrar(decInitialize)
213+
214+=head3 Decimation Filter C<"dec">
215+
216+This filter is used to reduce the number or rate of monitor updates from a
217+channel by an integer factor C<n> that is provided as a filter argument,
218+discarding the other updates. A true decimation following the original meaning
219+of the word would be achieved by giving C<n> as 10, to only allow every tenth
220+update through.
221+
222+=head4 Parameters
223+
224+=over
225+
226+=item Number C<"n">
227+
228+The decimation factor, a positive integer. Giving n=1 is equivalent to a no-op
229+that allows all updates to be passed to the client.
230+
231+=back
232+
233+This filter is intentionally very simplistic. It passes on the first monitor
234+event that it sees after the channel connects, then discards the next N-1 events
235+before sending the next event. If several clients connect to a channel using the
236+same filter settings they may see completely different data streams since each
237+client gets its own instance of the filter whose event counter starts when that
238+client connects.
239+
240+=head4 Example
241+
242+To sample a 60Hz channel at 1Hz, a 10Hz channel every 6 seconds or a 1Hz channel
243+once every minute:
244+
245+ Hal$ camonitor 'test:channel' 'test:channel.{"dec":{"n":60}}'
246+ ...
247+
248+=cut
249diff --git a/src/std/filters/sync.c b/src/std/filters/sync.c
250index c32e9af..6b841ea 100644
251--- a/src/std/filters/sync.c
252+++ b/src/std/filters/sync.c
253@@ -109,7 +109,9 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
254 passfl = pfl;
255 pfl = NULL;
256 }
257- break;
258+ else
259+ db_delete_field_log(pfl);
260+ goto save_state;
261 case syncModeLast:
262 if (!actstate && my->laststate) {
263 passfl = my->lastfl;
264@@ -121,28 +123,34 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
265 passfl = pfl;
266 pfl = NULL;
267 }
268- break;
269+ else
270+ db_delete_field_log(pfl);
271+ goto save_state;
272 case syncModeWhile:
273- if (actstate) {
274+ if (actstate)
275 passfl = pfl;
276- }
277+ else
278+ db_delete_field_log(pfl);
279 goto no_shift;
280 case syncModeUnless:
281- if (!actstate) {
282+ if (!actstate)
283 passfl = pfl;
284- }
285+ else
286+ db_delete_field_log(pfl);
287 goto no_shift;
288 }
289
290 if (my->lastfl)
291 db_delete_field_log(my->lastfl);
292 my->lastfl = pfl;
293- my->laststate = actstate;
294
295 /* since no copy is made we can't keep a reference to the returned fl */
296 assert(my->lastfl != passfl);
297
298- no_shift:
299+save_state:
300+ my->laststate = actstate;
301+
302+no_shift:
303 return passfl;
304 }
305
306diff --git a/src/std/filters/test/Makefile b/src/std/filters/test/Makefile
307index 6e6ad79..ad77c5d 100644
308--- a/src/std/filters/test/Makefile
309+++ b/src/std/filters/test/Makefile
310@@ -56,6 +56,12 @@ syncTest_SRCS += filterTest_registerRecordDeviceDriver.cpp
311 testHarness_SRCS += syncTest.c
312 TESTS += syncTest
313
314+TESTPROD_HOST += decTest
315+decTest_SRCS += decTest.c
316+decTest_SRCS += filterTest_registerRecordDeviceDriver.cpp
317+testHarness_SRCS += decTest.c
318+TESTS += decTest
319+
320 # epicsRunFilterTests runs all the test programs in a known working order.
321 testHarness_SRCS += epicsRunFilterTests.c
322
323diff --git a/src/std/filters/test/dbndTest.c b/src/std/filters/test/dbndTest.c
324index b35b9a6..4d70f83 100644
325--- a/src/std/filters/test/dbndTest.c
326+++ b/src/std/filters/test/dbndTest.c
327@@ -39,12 +39,14 @@ static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) {
328 static void fl_setup(dbChannel *chan, db_field_log *pfl) {
329 struct dbCommon *prec = dbChannelRecord(chan);
330
331+ memset(pfl, 0, sizeof(db_field_log));
332 pfl->ctx = dbfl_context_read;
333 pfl->type = dbfl_type_val;
334 pfl->stat = prec->stat;
335 pfl->sevr = prec->sevr;
336 pfl->time = prec->time;
337 pfl->field_type = dbChannelFieldType(chan);
338+ pfl->field_size = dbChannelFieldSize(chan);
339 pfl->no_elements = dbChannelElements(chan);
340 /*
341 * use memcpy to avoid a bus error on
342@@ -62,6 +64,7 @@ static void changeValue(db_field_log *pfl2, long val) {
343 }
344
345 static void mustPassOnce(dbChannel *pch, db_field_log *pfl2, char* m, double d, long val) {
346+ int oldFree = db_available_logs(), newFree;
347 db_field_log *pfl;
348
349 changeValue(pfl2, val);
350@@ -71,18 +74,26 @@ static void mustPassOnce(dbChannel *pch, db_field_log *pfl2, char* m, double d,
351 testOk(fl_equal(pfl, pfl2), "call 1 does not change field_log data");
352 pfl = dbChannelRunPreChain(pch, pfl2);
353 testOk(NULL == pfl, "call 2 drops field_log");
354+ newFree = db_available_logs();
355+ testOk(newFree == oldFree + 1, "field_log was freed - %d+1 => %d",
356+ oldFree, newFree);
357 }
358
359 static void mustDrop(dbChannel *pch, db_field_log *pfl2, char* m, double d, long val) {
360+ int oldFree = db_available_logs(), newFree;
361 db_field_log *pfl;
362
363 changeValue(pfl2, val);
364 testDiag("mode=%s delta=%g filter must drop", m, d);
365 pfl = dbChannelRunPreChain(pch, pfl2);
366 testOk(NULL == pfl, "call 1 drops field_log");
367+ newFree = db_available_logs();
368+ testOk(newFree == oldFree + 1, "field_log was freed - %d+1 => %d",
369+ oldFree, newFree);
370 }
371
372 static void mustPassTwice(dbChannel *pch, db_field_log *pfl2, char* m, double d, long val) {
373+ int oldFree = db_available_logs(), newFree;
374 db_field_log *pfl;
375
376 changeValue(pfl2, val);
377@@ -93,6 +104,9 @@ static void mustPassTwice(dbChannel *pch, db_field_log *pfl2, char* m, double d,
378 pfl = dbChannelRunPreChain(pch, pfl2);
379 testOk(pfl2 == pfl, "call 2 does not drop or replace field_log");
380 testOk(fl_equal(pfl, pfl2), "call 2 does not change field_log data");
381+ newFree = db_available_logs();
382+ testOk(newFree == oldFree, "field_log was not freed - %d => %d",
383+ oldFree, newFree);
384 }
385
386 static void testHead (char* title) {
387@@ -113,8 +127,9 @@ MAIN(dbndTest)
388 db_field_log *pfl2;
389 db_field_log fl1;
390 dbEventCtx evtctx;
391+ int logsFree, logsFinal;
392
393- testPlan(59);
394+ testPlan(77);
395
396 testdbPrepare();
397
398@@ -135,6 +150,11 @@ MAIN(dbndTest)
399 testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{}}")), "dbChannel with plugin dbnd (delta=0) created");
400 testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
401
402+ /* Start the free-list */
403+ db_delete_field_log(db_create_read_log(pch));
404+ logsFree = db_available_logs();
405+ testDiag("%d field_logs on free-list", logsFree);
406+
407 memset(&fl, PATTERN, sizeof(fl));
408 fl1 = fl;
409 node = ellFirst(&pch->filters);
410@@ -176,6 +196,8 @@ MAIN(dbndTest)
411
412 dbChannelDelete(pch);
413
414+ testDiag("%d field_logs on free-list", db_available_logs());
415+
416 /* Delta = -1: pass any update */
417
418 testHead("Delta = -1: pass any update");
419@@ -192,6 +214,8 @@ MAIN(dbndTest)
420 db_delete_field_log(pfl2);
421 dbChannelDelete(pch);
422
423+ testDiag("%d field_logs on free-list", db_available_logs());
424+
425 /* Delta = absolute */
426
427 testHead("Delta = absolute");
428@@ -224,6 +248,8 @@ MAIN(dbndTest)
429
430 dbChannelDelete(pch);
431
432+ testDiag("%d field_logs on free-list", db_available_logs());
433+
434 /* Delta = relative */
435
436 testHead("Delta = relative");
437@@ -275,6 +301,9 @@ MAIN(dbndTest)
438
439 dbChannelDelete(pch);
440
441+ logsFinal = db_available_logs();
442+ testOk(logsFree == logsFinal, "%d field_logs on free-list", logsFinal);
443+
444 db_close_events(evtctx);
445
446 testIocShutdownOk();
447diff --git a/src/std/filters/test/decTest.c b/src/std/filters/test/decTest.c
448new file mode 100644
449index 0000000..3b67842
450--- /dev/null
451+++ b/src/std/filters/test/decTest.c
452@@ -0,0 +1,289 @@
453+/*************************************************************************\
454+* Copyright (c) 2019 UChicago Argonne LLC, as Operator of Argonne
455+* National Laboratory.
456+* Copyright (c) 2010 Brookhaven National Laboratory.
457+* Copyright (c) 2010 Helmholtz-Zentrum Berlin
458+* fuer Materialien und Energie GmbH.
459+* EPICS BASE is distributed subject to a Software License Agreement found
460+* in file LICENSE that is included with this distribution.
461+\*************************************************************************/
462+
463+/*
464+ * Authors: Ralph Lange <Ralph.Lange@bessy.de>,
465+ * Andrew Johnson <anj@anl.gov>
466+ */
467+
468+#include <string.h>
469+
470+#include "dbStaticLib.h"
471+#include "dbAccessDefs.h"
472+#include "db_field_log.h"
473+#include "dbCommon.h"
474+#include "dbChannel.h"
475+#include "registry.h"
476+#include "chfPlugin.h"
477+#include "errlog.h"
478+#include "dbmf.h"
479+#include "epicsUnitTest.h"
480+#include "dbUnitTest.h"
481+#include "epicsTime.h"
482+#include "testMain.h"
483+#include "osiFileName.h"
484+
485+void filterTest_registerRecordDeviceDriver(struct dbBase *);
486+
487+static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) {
488+ return !(memcmp(pfl1, pfl2, sizeof(db_field_log)));
489+}
490+
491+static void fl_setup(dbChannel *chan, db_field_log *pfl, long val) {
492+ struct dbCommon *prec = dbChannelRecord(chan);
493+
494+ memset(pfl, 0, sizeof(db_field_log));
495+ pfl->ctx = dbfl_context_event;
496+ pfl->type = dbfl_type_val;
497+ pfl->stat = prec->stat;
498+ pfl->sevr = prec->sevr;
499+ pfl->time = prec->time;
500+ pfl->field_type = DBF_LONG;
501+ pfl->field_size = sizeof(epicsInt32);
502+ pfl->no_elements = 1;
503+ /*
504+ * use memcpy to avoid a bus error on
505+ * union copy of char in the db at an odd
506+ * address
507+ */
508+ memcpy(&pfl->u.v.field,
509+ dbChannelField(chan),
510+ dbChannelFieldSize(chan));
511+ pfl->u.v.field.dbf_long = val;
512+}
513+
514+static void testHead (char* title) {
515+ testDiag("--------------------------------------------------------");
516+ testDiag("%s", title);
517+ testDiag("--------------------------------------------------------");
518+}
519+
520+static void mustDrop(dbChannel *pch, db_field_log *pfl, char* m) {
521+ int oldFree = db_available_logs();
522+ db_field_log *pfl2 = dbChannelRunPreChain(pch, pfl);
523+ int newFree = db_available_logs();
524+
525+ testOk(NULL == pfl2, "filter drops field_log (%s)", m);
526+ testOk(newFree == oldFree + 1, "field_log was freed - %d+1 => %d",
527+ oldFree, newFree);
528+
529+ db_delete_field_log(pfl2);
530+}
531+
532+static void mustPass(dbChannel *pch, db_field_log *pfl, char* m) {
533+ int oldFree = db_available_logs();
534+ db_field_log *pfl2 = dbChannelRunPreChain(pch, pfl);
535+ int newFree = db_available_logs();
536+
537+ testOk(pfl == pfl2, "filter passes field_log (%s)", m);
538+ testOk(newFree == oldFree, "field_log was not freed - %d => %d",
539+ oldFree, newFree);
540+
541+ db_delete_field_log(pfl2);
542+}
543+
544+static void checkAndOpenChannel(dbChannel *pch, const chFilterPlugin *plug) {
545+ ELLNODE *node;
546+ chFilter *filter;
547+ chPostEventFunc *cb_out = NULL;
548+ void *arg_out = NULL;
549+ db_field_log fl, fl1;
550+
551+ testDiag("Test filter structure and open channel");
552+
553+ testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
554+
555+ fl_setup(pch, &fl, 1);
556+ fl1 = fl;
557+ node = ellFirst(&pch->filters);
558+ filter = CONTAINER(node, chFilter, list_node);
559+ plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1);
560+ testOk(cb_out && arg_out,
561+ "register_pre registers one filter with argument");
562+ testOk(fl_equal(&fl1, &fl),
563+ "register_pre does not change field_log data type");
564+
565+ testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dec opened");
566+ node = ellFirst(&pch->pre_chain);
567+ filter = CONTAINER(node, chFilter, pre_node);
568+ testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg != NULL),
569+ "dec has one filter with argument in pre chain");
570+ testOk((ellCount(&pch->post_chain) == 0),
571+ "sync has no filter in post chain");
572+}
573+
574+MAIN(decTest)
575+{
576+ dbChannel *pch;
577+ const chFilterPlugin *plug;
578+ char myname[] = "dec";
579+ db_field_log *pfl[10];
580+ int i, logsFree, logsFinal;
581+ dbEventCtx evtctx;
582+
583+ testPlan(104);
584+
585+ testdbPrepare();
586+
587+ testdbReadDatabase("filterTest.dbd", NULL, NULL);
588+
589+ filterTest_registerRecordDeviceDriver(pdbbase);
590+
591+ testdbReadDatabase("xRecord.db", NULL, NULL);
592+
593+ eltc(0);
594+ testIocInitOk();
595+ eltc(1);
596+
597+ evtctx = db_init_events();
598+
599+ testOk(!!(plug = dbFindFilter(myname, strlen(myname))),
600+ "plugin '%s' registered correctly", myname);
601+
602+ /* N < 1 */
603+ testOk(!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":-1}}")),
604+ "dbChannel with dec (n=-1) failed");
605+ testOk(!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":0}}")),
606+ "dbChannel with dec (n=0) failed");
607+ /* Bad parms */
608+ testOk(!(pch = dbChannelCreate("x.VAL{\"dec\":{}}")),
609+ "dbChannel with dec (no parm) failed");
610+ testOk(!(pch = dbChannelCreate("x.VAL{\"dec\":{\"x\":true}}")),
611+ "dbChannel with dec (x=true) failed");
612+
613+ /* No Decimation (N=1) */
614+
615+ testHead("No Decimation (n=1)");
616+ testOk(!!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":1}}")),
617+ "dbChannel with plugin dec (n=1) created");
618+
619+ /* Start the free-list */
620+ db_delete_field_log(db_create_read_log(pch));
621+ logsFree = db_available_logs();
622+ testDiag("%d field_logs on free-list", logsFree);
623+
624+ checkAndOpenChannel(pch, plug);
625+
626+ for (i = 0; i < 5; i++) {
627+ pfl[i] = db_create_read_log(pch);
628+ fl_setup(pch, pfl[i], 10 + i);
629+ }
630+
631+ testDiag("Test event stream");
632+
633+ mustPass(pch, pfl[0], "i=0");
634+ mustPass(pch, pfl[1], "i=1");
635+ mustPass(pch, pfl[2], "i=2");
636+ mustPass(pch, pfl[3], "i=3");
637+ mustPass(pch, pfl[4], "i=4");
638+
639+ dbChannelDelete(pch);
640+
641+ testDiag("%d field_logs on free-list", db_available_logs());
642+
643+ /* Decimation (N=2) */
644+
645+ testHead("Decimation (n=2)");
646+ testOk(!!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":2}}")),
647+ "dbChannel with plugin dec (n=2) created");
648+
649+ checkAndOpenChannel(pch, plug);
650+
651+ for (i = 0; i < 10; i++) {
652+ pfl[i] = db_create_read_log(pch);
653+ fl_setup(pch, pfl[i], 20 + i);
654+ }
655+
656+ testDiag("Test event stream");
657+
658+ mustPass(pch, pfl[0], "i=0");
659+ mustDrop(pch, pfl[1], "i=1");
660+ mustPass(pch, pfl[2], "i=2");
661+ mustDrop(pch, pfl[3], "i=3");
662+ mustPass(pch, pfl[4], "i=4");
663+ mustDrop(pch, pfl[5], "i=5");
664+ mustPass(pch, pfl[6], "i=6");
665+ mustDrop(pch, pfl[7], "i=7");
666+ mustPass(pch, pfl[8], "i=8");
667+ mustDrop(pch, pfl[9], "i=9");
668+
669+ dbChannelDelete(pch);
670+
671+ testDiag("%d field_logs on free-list", db_available_logs());
672+
673+ /* Decimation (N=3) */
674+
675+ testHead("Decimation (n=3)");
676+ testOk(!!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":3}}")),
677+ "dbChannel with plugin dec (n=3) created");
678+
679+ checkAndOpenChannel(pch, plug);
680+
681+ for (i = 0; i < 10; i++) {
682+ pfl[i] = db_create_read_log(pch);
683+ fl_setup(pch, pfl[i], 30 + i);
684+ }
685+
686+ testDiag("Test event stream");
687+
688+ mustPass(pch, pfl[0], "i=0");
689+ mustDrop(pch, pfl[1], "i=1");
690+ mustDrop(pch, pfl[2], "i=2");
691+ mustPass(pch, pfl[3], "i=3");
692+ mustDrop(pch, pfl[4], "i=4");
693+ mustDrop(pch, pfl[5], "i=5");
694+ mustPass(pch, pfl[6], "i=6");
695+ mustDrop(pch, pfl[7], "i=7");
696+ mustDrop(pch, pfl[8], "i=8");
697+ mustPass(pch, pfl[9], "i=9");
698+
699+ dbChannelDelete(pch);
700+
701+ testDiag("%d field_logs on free-list", db_available_logs());
702+
703+ /* Decimation (N=4) */
704+
705+ testHead("Decimation (n=4)");
706+ testOk(!!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":4}}")),
707+ "dbChannel with plugin dec (n=4) created");
708+
709+ checkAndOpenChannel(pch, plug);
710+
711+ for (i = 0; i < 10; i++) {
712+ pfl[i] = db_create_read_log(pch);
713+ fl_setup(pch, pfl[i], 40 + i);
714+ }
715+
716+ testDiag("Test event stream");
717+
718+ mustPass(pch, pfl[0], "i=0");
719+ mustDrop(pch, pfl[1], "i=1");
720+ mustDrop(pch, pfl[2], "i=2");
721+ mustDrop(pch, pfl[3], "i=3");
722+ mustPass(pch, pfl[4], "i=4");
723+ mustDrop(pch, pfl[5], "i=5");
724+ mustDrop(pch, pfl[6], "i=6");
725+ mustDrop(pch, pfl[7], "i=7");
726+ mustPass(pch, pfl[8], "i=8");
727+ mustDrop(pch, pfl[9], "i=9");
728+
729+ dbChannelDelete(pch);
730+
731+ logsFinal = db_available_logs();
732+ testOk(logsFree == logsFinal, "%d field_logs on free-list", logsFinal);
733+
734+ db_close_events(evtctx);
735+
736+ testIocShutdownOk();
737+
738+ testdbCleanup();
739+
740+ return testDone();
741+}
742diff --git a/src/std/filters/test/epicsRunFilterTests.c b/src/std/filters/test/epicsRunFilterTests.c
743index 2363643..5737d77 100644
744--- a/src/std/filters/test/epicsRunFilterTests.c
745+++ b/src/std/filters/test/epicsRunFilterTests.c
746@@ -17,6 +17,7 @@ int tsTest(void);
747 int dbndTest(void);
748 int syncTest(void);
749 int arrTest(void);
750+int decTest(void);
751
752 void epicsRunFilterTests(void)
753 {
754@@ -26,6 +27,7 @@ void epicsRunFilterTests(void)
755 runTest(dbndTest);
756 runTest(syncTest);
757 runTest(arrTest);
758+ runTest(decTest);
759
760 dbmfFreeChunks();
761
762diff --git a/src/std/filters/test/syncTest.c b/src/std/filters/test/syncTest.c
763index 9af44af..9be23b0 100644
764--- a/src/std/filters/test/syncTest.c
765+++ b/src/std/filters/test/syncTest.c
766@@ -42,12 +42,14 @@ static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) {
767 static void fl_setup(dbChannel *chan, db_field_log *pfl, long val) {
768 struct dbCommon *prec = dbChannelRecord(chan);
769
770+ memset(pfl, 0, sizeof(db_field_log));
771 pfl->ctx = dbfl_context_event;
772 pfl->type = dbfl_type_val;
773 pfl->stat = prec->stat;
774 pfl->sevr = prec->sevr;
775 pfl->time = prec->time;
776 pfl->field_type = DBF_LONG;
777+ pfl->field_size = sizeof(epicsInt32);
778 pfl->no_elements = 1;
779 /*
780 * use memcpy to avoid a bus error on
781@@ -66,31 +68,92 @@ static void testHead (char* title) {
782 testDiag("--------------------------------------------------------");
783 }
784
785-static void mustDrop(dbChannel *pch, db_field_log *pfl2, char* m) {
786- db_field_log *pfl = dbChannelRunPreChain(pch, pfl2);
787- testOk(NULL == pfl, "filter drops field_log (%s)", m);
788+/*
789+ * Use mustDrop() and mustPass() to test filters with no memory
790+ * of previous field_log pointers.
791+ */
792+static void mustDrop(dbChannel *pch, db_field_log *pfl, char* m) {
793+ int oldFree = db_available_logs();
794+ db_field_log *pfl2 = dbChannelRunPreChain(pch, pfl);
795+ int newFree = db_available_logs();
796+
797+ testOk(NULL == pfl2, "filter drops field_log (%s)", m);
798+ testOk(newFree == oldFree + 1, "a field_log was freed - %d+1 => %d",
799+ oldFree, newFree);
800+
801+ db_delete_field_log(pfl2);
802 }
803
804-static void mustPassTwice(dbChannel *pch, db_field_log *pfl2, char* m) {
805- db_field_log *pfl;
806+static void mustPass(dbChannel *pch, db_field_log *pfl, char* m) {
807+ int oldFree = db_available_logs();
808+ db_field_log *pfl2 = dbChannelRunPreChain(pch, pfl);
809+ int newFree = db_available_logs();
810
811- testDiag("%s: filter must pass twice", m);
812- pfl = dbChannelRunPreChain(pch, pfl2);
813- testOk(pfl2 == pfl, "call 1 does not drop or replace field_log");
814- pfl = dbChannelRunPreChain(pch, pfl2);
815- testOk(pfl2 == pfl, "call 2 does not drop or replace field_log");
816+ testOk(pfl == pfl2, "filter passes field_log (%s)", m);
817+ testOk(newFree == oldFree, "no field_logs were freed - %d => %d",
818+ oldFree, newFree);
819+
820+ db_delete_field_log(pfl2);
821 }
822
823-static void mustPassOld(dbChannel *pch, db_field_log *old, db_field_log *cur, char* m) {
824- db_field_log *pfl = dbChannelRunPreChain(pch, cur);
825+/*
826+ * Use mustStash() and mustSwap() to test filters that save
827+ * field_log pointers and return them later.
828+ *
829+ * mustStash() expects the filter to save the current pointer
830+ * (freeing any previously saved pointer) and return NULL.
831+ * mustSwap() expects the filter to return the previously
832+ * saved pointer and save the current pointer.
833+ */
834+static db_field_log *stashed;
835
836- testOk(old == pfl, "filter passes previous field log (%s)", m);
837+static void streamReset(void) {
838+ stashed = NULL;
839 }
840
841-static void mustPass(dbChannel *pch, db_field_log *cur, char* m) {
842- db_field_log *pfl = dbChannelRunPreChain(pch, cur);
843+static void mustStash(dbChannel *pch, db_field_log *pfl, char* m) {
844+ int oldFree = db_available_logs();
845+ db_field_log *pfl2 = dbChannelRunPreChain(pch, pfl);
846+ int newFree = db_available_logs();
847
848- testOk(cur == pfl, "filter passes field_log (%s)", m);
849+ testOk(NULL == pfl2, "filter stashes field_log (%s)", m);
850+ if (stashed) {
851+ testOk(newFree == oldFree + 1, "a field_log was freed - %d+1 => %d",
852+ oldFree, newFree);
853+ }
854+ else {
855+ testOk(newFree == oldFree, "no field_logs were freed - %d => %d",
856+ oldFree, newFree);
857+ }
858+ stashed = pfl;
859+ db_delete_field_log(pfl2);
860+}
861+
862+static void mustSwap(dbChannel *pch, db_field_log *pfl, char* m) {
863+ int oldFree = db_available_logs();
864+ db_field_log *pfl2 = dbChannelRunPreChain(pch, pfl);
865+ int newFree = db_available_logs();
866+
867+ testOk(stashed == pfl2, "filter returns stashed field log (%s)", m);
868+ testOk(newFree == oldFree, "no field_logs were freed - %d => %d",
869+ oldFree, newFree);
870+
871+ stashed = pfl;
872+ db_delete_field_log(pfl2);
873+}
874+
875+static void mustPassTwice(dbChannel *pch, db_field_log *pfl, char* m) {
876+ int oldFree = db_available_logs(), newFree;
877+ db_field_log *pfl2;
878+
879+ testDiag("%s: filter must pass twice", m);
880+ pfl2 = dbChannelRunPreChain(pch, pfl);
881+ testOk(pfl2 == pfl, "call 1 does not drop or replace field_log");
882+ pfl2 = dbChannelRunPreChain(pch, pfl);
883+ testOk(pfl2 == pfl, "call 2 does not drop or replace field_log");
884+ newFree = db_available_logs();
885+ testOk(newFree == oldFree, "no field_logs were freed - %d => %d",
886+ oldFree, newFree);
887 }
888
889 static void checkCtxRead(dbChannel *pch, dbStateId id) {
890@@ -138,10 +201,10 @@ MAIN(syncTest)
891 const chFilterPlugin *plug;
892 char myname[] = "sync";
893 db_field_log *pfl[10];
894- int i;
895+ int i, logsFree, logsFinal;
896 dbEventCtx evtctx;
897
898- testPlan(139);
899+ testPlan(214);
900
901 testdbPrepare();
902
903@@ -176,9 +239,14 @@ MAIN(syncTest)
904 testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"while\",\"s\":\"red\"}}")),
905 "dbChannel with plugin sync (m='while' s='red') created");
906
907+ /* Start the free-list */
908+ db_delete_field_log(db_create_read_log(pch));
909+ logsFree = db_available_logs();
910+ testDiag("%d field_logs on free-list", logsFree);
911+
912 checkAndOpenChannel(pch, plug);
913
914- for (i = 0; i < 10; i++) {
915+ for (i = 0; i < 9; i++) {
916 pfl[i] = db_create_read_log(pch);
917 fl_setup(pch, pfl[i], 120 + i);
918 }
919@@ -198,11 +266,10 @@ MAIN(syncTest)
920 mustDrop(pch, pfl[7], "state=FALSE, log7");
921 mustDrop(pch, pfl[8], "state=FALSE, log8");
922
923- for (i = 0; i < 10; i++)
924- db_delete_field_log(pfl[i]);
925-
926 dbChannelDelete(pch);
927
928+ testDiag("%d field_logs on free-list", db_available_logs());
929+
930 /* mode UNLESS */
931
932 testHead("Mode UNLESS (m='unless', s='red')");
933@@ -211,7 +278,7 @@ MAIN(syncTest)
934
935 checkAndOpenChannel(pch, plug);
936
937- for (i = 0; i < 10; i++) {
938+ for (i = 0; i < 9; i++) {
939 pfl[i] = db_create_read_log(pch);
940 fl_setup(pch, pfl[i], 120 + i);
941 }
942@@ -231,11 +298,10 @@ MAIN(syncTest)
943 mustPass(pch, pfl[7], "state=FALSE, log7");
944 mustPass(pch, pfl[8], "state=FALSE, log8");
945
946- for (i = 0; i < 10; i++)
947- db_delete_field_log(pfl[i]);
948-
949 dbChannelDelete(pch);
950
951+ testDiag("%d field_logs on free-list", db_available_logs());
952+
953 /* mode BEFORE */
954
955 testHead("Mode BEFORE (m='before', s='red')");
956@@ -251,24 +317,25 @@ MAIN(syncTest)
957
958 testDiag("Test event stream");
959
960+ streamReset();
961 dbStateClear(red);
962- mustDrop(pch, pfl[0], "state=FALSE, log0");
963- mustDrop(pch, pfl[1], "state=FALSE, log1");
964- mustDrop(pch, pfl[2], "state=FALSE, log2");
965+ mustStash(pch, pfl[0], "state=FALSE, log0");
966+ mustStash(pch, pfl[1], "state=FALSE, log1");
967+ mustStash(pch, pfl[2], "state=FALSE, log2");
968 dbStateSet(red);
969- mustPassOld(pch, pfl[2], pfl[3], "state=TRUE, log3, pass=log2");
970- mustDrop(pch, pfl[4], "state=TRUE, log4");
971- mustDrop(pch, pfl[5], "state=TRUE, log5");
972- mustDrop(pch, pfl[6], "state=TRUE, log6");
973+ mustSwap(pch, pfl[3], "state=TRUE, log3");
974+ mustStash(pch, pfl[4], "state=TRUE, log4");
975+ mustStash(pch, pfl[5], "state=TRUE, log5");
976+ mustStash(pch, pfl[6], "state=TRUE, log6");
977 dbStateClear(red);
978- mustDrop(pch, pfl[7], "state=FALSE, log7");
979- mustDrop(pch, pfl[8], "state=FALSE, log8");
980- mustDrop(pch, pfl[9], "state=FALSE, log9");
981-
982- db_delete_field_log(pfl[2]);
983+ mustStash(pch, pfl[7], "state=FALSE, log7");
984+ mustStash(pch, pfl[8], "state=FALSE, log8");
985+ mustStash(pch, pfl[9], "state=FALSE, log9");
986
987 dbChannelDelete(pch);
988
989+ testDiag("%d field_logs on free-list", db_available_logs());
990+
991 /* mode FIRST */
992
993 testHead("Mode FIRST (m='first', s='red')");
994@@ -277,13 +344,14 @@ MAIN(syncTest)
995
996 checkAndOpenChannel(pch, plug);
997
998- for (i = 0; i < 10; i++) {
999+ for (i = 0; i < 9; i++) {
1000 pfl[i] = db_create_read_log(pch);
1001 fl_setup(pch, pfl[i], 120 + i);
1002 }
1003
1004 testDiag("Test event stream");
1005
1006+ streamReset();
1007 dbStateClear(red);
1008 mustDrop(pch, pfl[0], "state=FALSE, log0");
1009 mustDrop(pch, pfl[1], "state=FALSE, log1");
1010@@ -297,11 +365,10 @@ MAIN(syncTest)
1011 mustDrop(pch, pfl[7], "state=FALSE, log7");
1012 mustDrop(pch, pfl[8], "state=FALSE, log8");
1013
1014- db_delete_field_log(pfl[3]);
1015- db_delete_field_log(pfl[9]);
1016-
1017 dbChannelDelete(pch);
1018
1019+ testDiag("%d field_logs on free-list", db_available_logs());
1020+
1021 /* mode LAST */
1022
1023 testHead("Mode LAST (m='last', s='red')");
1024@@ -317,24 +384,25 @@ MAIN(syncTest)
1025
1026 testDiag("Test event stream");
1027
1028+ streamReset();
1029 dbStateClear(red);
1030- mustDrop(pch, pfl[0], "state=FALSE, log0");
1031- mustDrop(pch, pfl[1], "state=FALSE, log1");
1032- mustDrop(pch, pfl[2], "state=FALSE, log2");
1033+ mustStash(pch, pfl[0], "state=FALSE, log0");
1034+ mustStash(pch, pfl[1], "state=FALSE, log1");
1035+ mustStash(pch, pfl[2], "state=FALSE, log2");
1036 dbStateSet(red);
1037- mustDrop(pch, pfl[3], "state=TRUE, log3");
1038- mustDrop(pch, pfl[4], "state=TRUE, log4");
1039- mustDrop(pch, pfl[5], "state=TRUE, log5");
1040+ mustStash(pch, pfl[3], "state=TRUE, log3");
1041+ mustStash(pch, pfl[4], "state=TRUE, log4");
1042+ mustStash(pch, pfl[5], "state=TRUE, log5");
1043 dbStateClear(red);
1044- mustPassOld(pch, pfl[5], pfl[6], "state=TRUE, log6, pass=log5");
1045- mustDrop(pch, pfl[7], "state=FALSE, log7");
1046- mustDrop(pch, pfl[8], "state=FALSE, log8");
1047- mustDrop(pch, pfl[9], "state=FALSE, log9");
1048-
1049- db_delete_field_log(pfl[5]);
1050+ mustSwap(pch, pfl[6], "state=TRUE, log6");
1051+ mustStash(pch, pfl[7], "state=FALSE, log7");
1052+ mustStash(pch, pfl[8], "state=FALSE, log8");
1053+ mustStash(pch, pfl[9], "state=FALSE, log9");
1054
1055 dbChannelDelete(pch);
1056
1057+ testDiag("%d field_logs on free-list", db_available_logs());
1058+
1059 /* mode AFTER */
1060
1061 testHead("Mode AFTER (m='after', s='red')");
1062@@ -343,13 +411,14 @@ MAIN(syncTest)
1063
1064 checkAndOpenChannel(pch, plug);
1065
1066- for (i = 0; i < 10; i++) {
1067+ for (i = 0; i < 9; i++) {
1068 pfl[i] = db_create_read_log(pch);
1069 fl_setup(pch, pfl[i], 120 + i);
1070 }
1071
1072 testDiag("Test event stream");
1073
1074+ streamReset();
1075 dbStateClear(red);
1076 mustDrop(pch, pfl[0], "state=FALSE, log0");
1077 mustDrop(pch, pfl[1], "state=FALSE, log1");
1078@@ -363,11 +432,11 @@ MAIN(syncTest)
1079 mustDrop(pch, pfl[7], "state=FALSE, log7");
1080 mustDrop(pch, pfl[8], "state=FALSE, log8");
1081
1082- db_delete_field_log(pfl[6]);
1083- db_delete_field_log(pfl[9]);
1084-
1085 dbChannelDelete(pch);
1086
1087+ logsFinal = db_available_logs();
1088+ testOk(logsFree == logsFinal, "%d field_logs on free-list", logsFinal);
1089+
1090 db_close_events(evtctx);
1091
1092 testIocShutdownOk();

Subscribers

People subscribed via source and target branches