14 maintenanceInterval = 10000;
34 std::multimap<uint64, DataMessage*>::iterator i = eolMap.begin(), e = eolMap.end();
43 this->defaultTTL = defaultTTL;
50 if ((
stricmp(key,
"time") == 0) && !timeMap) {
51 timeMap =
new std::multimap<uint64, DataMessage*>;
75 if ((
stricmp(key,
"time") == 0) && timeMap) {
81 return deleteIndexKey<uint64>(key, timeMaps);
83 return deleteIndexKey<std::string>(key, stringMaps);
85 return deleteIndexKey<int64>(key, integerMaps);
87 return deleteIndexKey<float64>(key, floatMaps);
96 this->indexfilename = indexfilename;
97 this->buffertime = buffertime;
102 return (uint32)eolMap.size();
106 if (
GetTimeAgeMS(lastMaintenance) > maintenanceInterval)
110 addMessageToTime(msg);
111 addMessageToString(msg);
112 addMessageToInteger(msg);
113 addMessageToFloat(msg);
118 typeMap->insert(Type_Pair(msg->
getType(), msg));
123 eolMap.insert(Time_Pair(msg->
getCreatedTime() + defaultTTL, msg));
125 eolMap.insert(Time_Pair(msg->
getEOL(), msg));
130 if (
GetTimeAgeMS(lastMaintenance) > maintenanceInterval)
137 std::map<std::string, std::multimap<uint64, DataMessage*> >::iterator it;
138 std::map<std::string, std::multimap<std::string, DataMessage*> >::iterator is;
139 std::map<std::string, std::multimap<int64, DataMessage*> >::iterator ii;
140 std::map<std::string, std::multimap<float64, DataMessage*> >::iterator ifl;
146 if ((
stricmp(spec->
key,
"time") == 0) && timeMap) {
155 it = timeMaps.find(spec->
key);
156 if (it != timeMaps.end()) {
168 is = stringMaps.find(spec->
key);
169 if (is != stringMaps.end()) {
172 if (start.size() && end.size())
182 ii = integerMaps.find(spec->
key);
183 if (ii != integerMaps.end()) {
195 ifl = floatMaps.find(spec->
key);
196 if (ifl != floatMaps.end()) {
222 std::map<std::string, std::multimap<uint64, DataMessage*> >::iterator it, et = timeMaps.end();
223 std::map<std::string, std::multimap<std::string, DataMessage*> >::iterator is, es = stringMaps.end();
224 std::map<std::string, std::multimap<int64, DataMessage*> >::iterator ii, ei = integerMaps.end();
225 std::map<std::string, std::multimap<float64, DataMessage*> >::iterator ifl, efl = floatMaps.end();
227 std::multimap<uint64, DataMessage*>::iterator i = eolMap.begin(), e = eolMap.end();
229 if (i->first < now) {
232 uint64 ctval = i->second->getCreatedTime();
234 removeMessage<uint64>(i->second, *timeMap, ctval);
237 it = timeMaps.begin();
239 uint64 tval = i->second->getTime(it->first.c_str());
241 removeMessage<uint64>(i->second, it->second, tval);
245 is = stringMaps.begin();
247 std::string sval = i->second->getString(is->first.c_str());
248 removeMessage<std::string>(i->second, is->second, sval);
252 ii = integerMaps.begin();
255 if (i->second->getInt(ii->first.c_str(), ival))
256 removeMessage<int64>(i->second, ii->second, ival);
260 ifl = floatMaps.begin();
263 if (i->second->getDouble(ifl->first.c_str(), fval))
264 removeMessage<float64>(i->second, ifl->second, fval);
289bool MessageIndex::addMessageToTime(
DataMessage* msg) {
291 std::map<std::string, std::multimap<uint64, DataMessage*> >::iterator i = timeMaps.begin(), e = timeMaps.end();
293 if (val = msg->
getTime(i->first.c_str()))
294 i->second.insert(Time_Pair(val, msg));
300bool MessageIndex::addMessageToString(
DataMessage* msg) {
302 std::map<std::string, std::multimap<std::string, DataMessage*> >::iterator i = stringMaps.begin(), e = stringMaps.end();
304 if (val = msg->getString(i->first.c_str()))
305 i->second.insert(String_Pair(val, msg));
311bool MessageIndex::addMessageToInteger(
DataMessage* msg) {
313 std::map<std::string, std::multimap<int64, DataMessage*> >::iterator i = integerMaps.begin(), e = integerMaps.end();
315 if (msg->getInt(i->first.c_str(), val)) {
316 i->second.insert(Int_Pair(val, msg));
324bool MessageIndex::addMessageToFloat(
DataMessage* msg) {
326 std::map<std::string, std::multimap<float64, DataMessage*> >::iterator i = floatMaps.begin(), e = floatMaps.end();
328 if (msg->getDouble(i->first.c_str(), val))
329 i->second.insert(Float_Pair(val, msg));
339bool MessageIndex::deleteIndexKey(
const char* key, std::map<std::string, std::multimap<T, DataMessage*> > &maps) {
341 std::multimap<T, DataMessage*>* map = &(maps[key]);
343 typename std::multimap<T, DataMessage*>::iterator i = map->begin(), e = map->end();
353bool MessageIndex::removeMessage(
DataMessage* msg, std::multimap<T, DataMessage*> &map, T &val) {
354 typename std::multimap<T, DataMessage*>::iterator i, e = map.end();
355 i = map.lower_bound(val);
356 while ((i != e) && (i->first == val)) {
357 if (((
void*)msg) == ((
void*)i->second))
366char*
MessageIndex::queryMessages(T start, T end, uint32 from, uint32 to, PsyType &type, uint32 maxcount, uint64 maxage, uint32 &size, uint32 &count, std::multimap<T, DataMessage*> &map) {
368 typename std::multimap<T, DataMessage*>::iterator i1, i2, e = map.end();
371 uint64 createdAfter = 0;
373 createdAfter = now - maxage;
375 if ( (i1 = map.lower_bound(start)) == e) {
379 i2 = map.upper_bound(end);
381 uint32 allocSize = 4096;
382 char* result = (
char*) malloc(allocSize);
386 while ((i1 != i2) && (i1 != e)) {
387 if (msg = i1->second) {
389 if (msg->getEOL() < now) {
393 if (from && (from != msg->getFrom())) {
397 if (to && (to != msg->getTo())) {
401 if ((type !=
NOTYPE) && (!msg->getType().matches(type))) {
405 if (maxcount && (count >= maxcount))
407 if (createdAfter && (msg->getCreatedTime() < createdAfter)) {
412 msgSize = i1->second->getSize();
413 while (size + msgSize > allocSize) {
415 void* newmem = realloc(result, allocSize);
421 result = (
char*) newmem;
423 memcpy(result+size, msg->data, msgSize);
434char*
MessageIndex::queryMessages(T start, uint32 from, uint32 to, PsyType &type, uint32 maxcount, uint64 maxage, uint32 &size, uint32 &count, std::multimap<T, DataMessage*> &map) {
436 typename std::multimap<T, DataMessage*>::iterator i1, i2, e = map.end();
439 uint64 createdAfter = 0;
441 createdAfter = now - maxage;
443 if ( (i1 = map.lower_bound(start)) == e)
446 uint32 allocSize = 4096;
447 char* result = (
char*) malloc(allocSize);
452 if (msg = i1->second) {
454 if (msg->getEOL() < now) {
458 if (from && (from != msg->getFrom())) {
462 if (to && (to != msg->getTo())) {
466 if ((type !=
NOTYPE) && (!msg->getType().matches(type))) {
470 if (maxcount && (count >= maxcount))
472 if (createdAfter && (msg->getCreatedTime() < createdAfter)) {
477 msgSize = i1->second->getSize();
478 while (size + msgSize > allocSize) {
480 void* newmem = realloc(result, allocSize);
486 result = (
char*) newmem;
488 memcpy(result+size, msg->data, msgSize);
499char*
MessageIndex::queryMessages(uint32 from, uint32 to, PsyType &type, uint32 maxcount, uint64 maxage, uint32 &size, uint32 &count, std::multimap<T, DataMessage*> &map) {
501 typename std::multimap<T, DataMessage*>::reverse_iterator i1, e = map.rend();
504 uint64 createdAfter = 0;
506 createdAfter = now - maxage;
508 if ( (i1 = map.rbegin()) == e) {
513 uint32 allocSize = 4096;
514 char* result = (
char*) malloc(allocSize);
519 if (msg = i1->second) {
521 if (msg->getTTL() && msg->getEOL() < now) {
525 if (from && (from != msg->getFrom())) {
529 if (to && (to != msg->getTo())) {
533 if ((type !=
NOTYPE) && (!msg->getType().matches(type))) {
537 if (maxcount && (count >= maxcount))
539 if (createdAfter && (msg->getCreatedTime() < createdAfter)) {
544 msgSize = i1->second->getSize();
545 while (size + msgSize > allocSize) {
547 void* newmem = realloc(result, allocSize);
553 result = (
char*) newmem;
555 memcpy(result+size, msg->data, msgSize);
574 uint32 n, size, count;
582 for (n=0; n<100; n++) {
589 double insertUs = (double)(
GetTimeNow() - t0);
592 unittest::metric(
"insert_throughput", 100.0 / insertUs * 1e6,
"msg/s",
true);
603 if ( (count < 40) || (count > 50) ) {
604 unittest::fail(
"MessageIndex test: maxage query didn't return around 50 msgs (got %u)", count);
611 for (n=0; n<count; n++) {
613 unittest::fail(
"MessageIndex test: maxage query result %u not a DataMessage", n);
619 if (!msg->
getInt(
"Count", val) || (val != 99 - n)) {
620 unittest::fail(
"MessageIndex test: maxage query ordering mismatch at %u (expected %lld)", n, (int64)(99 - n));
641 unittest::fail(
"MessageIndex test: maxcount query didn't return 50 msgs (got %u)", count);
648 for (n=0; n<count; n++) {
650 unittest::fail(
"MessageIndex test: maxcount query result %u not a DataMessage", n);
656 if (!msg->
getInt(
"Count", val) || (val != 99 - n)) {
657 unittest::fail(
"MessageIndex test: maxcount query ordering mismatch at %u (expected %lld)", n, (int64)(99 - n));
680 unittest::fail(
"MessageIndex test: post-sleep query returned more than 50 msgs (got %u)", count);
695 "Message index time-based insert and query (maxage, maxcount, age-out)",
"core");
In-memory, multi-key index over DataMessages with TTL-based expiry — the storage engine behind whiteb...
#define GetObjID(data)
Extract the cid field from a binary object block: the uint32 at byte offset 4 (after the leading size...
#define INDEX_FLOAT
Index the entry as a float64.
#define INDEX_INTEGER
Index the entry as an int64.
#define INDEX_TIME
Index the entry as a time (uint64 microseconds).
#define INDEX_STRING
Index the entry as a string.
static struct PsyType NOTYPE
The empty/unset message type (isValid() == false).
Small, dependency-free unit test harness used by all CMSDK object tests.
The central Psyclone data container: a self-contained binary message with typed, named user entries.
bool setTTL(uint64 ttl)
setTTL(uint64 ttl)
bool getInt(const char *key, int64 &value)
getInt(const char* key, int64& value)
bool setInt(const char *key, int64 value)
setInt(const char* key, int64 value)
PsyType getType()
getType()
uint32 getSize()
getSize() Get message size Many types of data of any size can be put into a message as user entries; ...
bool setCreatedTime(uint64 t)
setCreatedTime(uint64 t)
uint64 getTime(const char *key)
getTime(const char* key)
uint64 getCreatedTime()
getCreatedTime()
bool setFileStorage(const char *dir, uint32 buffertime, const char *indexfilename="index.db")
Enable file-backed storage of indexed messages.
char * queryMessages(RetrieveSpec *spec, uint32 &size, uint32 &count)
Execute a retrieval query and return matching messages as one flat buffer.
static bool UnitTest()
Run the built-in self test.
bool addIndexKey(const char *key, uint8 type)
Start indexing messages by the value of a named user entry.
~MessageIndex()
Destroys the index and deletes all messages it still holds.
bool addMessage(DataMessage *msg)
Add a message to the index; the index TAKES OWNERSHIP of msg and will delete it on expiry.
bool doMaintenance()
Perform periodic housekeeping: evict expired messages (per TTL/EOL) and flush file storage buffers.
bool removeIndexKey(const char *key, uint8 type)
Remove a previously added index key (the index over that entry is dropped; the messages themselves re...
MessageIndex()
Create an empty index with no extra index keys and no file storage.
bool setDefaultTTL(uint64 defaultTTL)
Set the TTL applied to messages that carry none of their own.
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
int32 GetTimeAgeMS(uint64 t)
Age of a timestamp relative to now, in milliseconds.
bool Sleep(uint32 ms)
Suspend the calling thread.
uint32 strcpyavail(char *dst, const char *src, uint32 maxlen, bool copyAvailable)
Bounded strcpy that always NUL-terminates.
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void detail(const char *fmt,...)
Verbose-only indented diagnostic line (shown only when verbose=1).
void progress(int percent, const char *action)
Report progress with a short description of the current action.
void Register_MessageIndex_Tests()
Specification of a whiteboard retrieval attached to a TriggerSpec.
uint64 endTime
Range end when keytype == INDEX_TIME (microseconds).
char endString[MAXVALUENAMELEN+1]
Range end when keytype == INDEX_STRING.
char startString[MAXVALUENAMELEN+1]
Range start when keytype == INDEX_STRING.
uint64 startTime
Range start when keytype == INDEX_TIME (microseconds).
uint8 keytype
Interpretation of key: INDEX_TIME, INDEX_STRING, INDEX_INTEGER or INDEX_FLOAT.
int64 endInt
Range end when keytype == INDEX_INTEGER.
int64 startInt
Range start when keytype == INDEX_INTEGER.
PsyType type
Message type to match (wildcards allowed).
uint32 maxcount
Maximum number of messages to return (0 = unlimited).
uint32 from
Restrict to messages sent by this component id (0 = any).
float64 startFloat
Range start when keytype == INDEX_FLOAT.
float64 endFloat
Range end when keytype == INDEX_FLOAT.
uint64 maxage
Maximum message age in MICROSECONDS (XML gives ms; parser multiplies by 1000).
char key[MAXKEYNAMELEN+1]
Indexed user entry to range-query on (empty = time/type retrieval only).
uint32 to
Restrict to messages addressed to this component id (0 = any).