number of samples casn be configured for table thresholds
[public/netxms.git] / src / server / core / dctthreshold.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2017 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: dctthreshold.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 /**
26 * Create new threshold instance
27 */
28 DCTableThresholdInstance::DCTableThresholdInstance(const TCHAR *name, int matchCount, bool active)
29 {
30 m_name = _tcsdup(name);
31 m_matchCount = matchCount;
32 m_active = active;
33 }
34
35 /**
36 * Copy constructor threshold instance
37 */
38 DCTableThresholdInstance::DCTableThresholdInstance(const DCTableThresholdInstance *src)
39 {
40 m_name = _tcsdup(src->m_name);
41 m_matchCount = src->m_matchCount;
42 m_active = src->m_active;
43 }
44
45 /**
46 * Threshold instance destructor
47 */
48 DCTableThresholdInstance::~DCTableThresholdInstance()
49 {
50 free(m_name);
51 }
52
53 /**
54 * Create new condition
55 */
56 DCTableCondition::DCTableCondition(const TCHAR *column, int operation, const TCHAR *value)
57 {
58 m_column = _tcsdup(CHECK_NULL_EX(column));
59 m_operation = operation;
60 m_value = value;
61 }
62
63 /**
64 * Copy constructor
65 */
66 DCTableCondition::DCTableCondition(DCTableCondition *src)
67 {
68 m_column = _tcsdup(src->m_column);
69 m_operation = src->m_operation;
70 m_value = src->m_value;
71 }
72
73 /**
74 * Condition destructor
75 */
76 DCTableCondition::~DCTableCondition()
77 {
78 free(m_column);
79 }
80
81 /**
82 * Check if condition is true
83 */
84 bool DCTableCondition::check(Table *value, int row)
85 {
86 int col = value->getColumnIndex(m_column);
87 if (col == -1)
88 return false;
89
90 int dt = value->getColumnDataType(col);
91 bool result = false;
92 switch(m_operation)
93 {
94 case OP_LE: // Less
95 switch(dt)
96 {
97 case DCI_DT_INT:
98 result = (value->getAsInt(row, col) < (INT32)m_value);
99 break;
100 case DCI_DT_UINT:
101 result = (value->getAsUInt(row, col) < (UINT32)m_value);
102 break;
103 case DCI_DT_INT64:
104 result = (value->getAsInt64(row, col) < (INT64)m_value);
105 break;
106 case DCI_DT_UINT64:
107 result = (value->getAsUInt64(row, col) < (UINT64)m_value);
108 break;
109 case DCI_DT_FLOAT:
110 result = (value->getAsDouble(row, col) < (double)m_value);
111 break;
112 }
113 break;
114 case OP_LE_EQ: // Less or equal
115 switch(dt)
116 {
117 case DCI_DT_INT:
118 result = (value->getAsInt(row, col) <= (INT32)m_value);
119 break;
120 case DCI_DT_UINT:
121 result = (value->getAsUInt(row, col) <= (UINT32)m_value);
122 break;
123 case DCI_DT_INT64:
124 result = (value->getAsInt64(row, col) <= (INT64)m_value);
125 break;
126 case DCI_DT_UINT64:
127 result = (value->getAsUInt64(row, col) <= (UINT64)m_value);
128 break;
129 case DCI_DT_FLOAT:
130 result = (value->getAsDouble(row, col) <= (double)m_value);
131 break;
132 }
133 break;
134 case OP_EQ: // Equal
135 switch(dt)
136 {
137 case DCI_DT_INT:
138 result = (value->getAsInt(row, col) == (INT32)m_value);
139 break;
140 case DCI_DT_UINT:
141 result = (value->getAsUInt(row, col) == (UINT32)m_value);
142 break;
143 case DCI_DT_INT64:
144 result = (value->getAsInt64(row, col) == (INT64)m_value);
145 break;
146 case DCI_DT_UINT64:
147 result = (value->getAsUInt64(row, col) == (UINT64)m_value);
148 break;
149 case DCI_DT_FLOAT:
150 result = (value->getAsDouble(row, col) == (double)m_value);
151 break;
152 case DCI_DT_STRING:
153 result = !_tcscmp(value->getAsString(row, col, _T("")), m_value.getString());
154 break;
155 }
156 break;
157 case OP_GT_EQ: // Greater or equal
158 switch(dt)
159 {
160 case DCI_DT_INT:
161 result = (value->getAsInt(row, col) >= (INT32)m_value);
162 break;
163 case DCI_DT_UINT:
164 result = (value->getAsUInt(row, col) >= (UINT32)m_value);
165 break;
166 case DCI_DT_INT64:
167 result = (value->getAsInt64(row, col) >= (INT64)m_value);
168 break;
169 case DCI_DT_UINT64:
170 result = (value->getAsUInt64(row, col) >= (UINT64)m_value);
171 break;
172 case DCI_DT_FLOAT:
173 result = (value->getAsDouble(row, col) >= (double)m_value);
174 break;
175 }
176 break;
177 case OP_GT: // Greater
178 switch(dt)
179 {
180 case DCI_DT_INT:
181 result = (value->getAsInt(row, col) > (INT32)m_value);
182 break;
183 case DCI_DT_UINT:
184 result = (value->getAsUInt(row, col) > (UINT32)m_value);
185 break;
186 case DCI_DT_INT64:
187 result = (value->getAsInt64(row, col) > (INT64)m_value);
188 break;
189 case DCI_DT_UINT64:
190 result = (value->getAsUInt64(row, col) > (UINT64)m_value);
191 break;
192 case DCI_DT_FLOAT:
193 result = (value->getAsDouble(row, col) > (double)m_value);
194 break;
195 }
196 break;
197 case OP_NE: // Not equal
198 switch(dt)
199 {
200 case DCI_DT_INT:
201 result = (value->getAsInt(row, col) != (INT32)m_value);
202 break;
203 case DCI_DT_UINT:
204 result = (value->getAsUInt(row, col) != (UINT32)m_value);
205 break;
206 case DCI_DT_INT64:
207 result = (value->getAsInt64(row, col) != (INT64)m_value);
208 break;
209 case DCI_DT_UINT64:
210 result = (value->getAsUInt64(row, col) != (UINT64)m_value);
211 break;
212 case DCI_DT_FLOAT:
213 result = (value->getAsDouble(row, col) != (double)m_value);
214 break;
215 case DCI_DT_STRING:
216 result = _tcscmp(value->getAsString(row, col, _T("")), m_value.getString()) ? true : false;
217 break;
218 }
219 break;
220 case OP_LIKE:
221 result = MatchString(m_value.getString(), value->getAsString(row, col, _T("")), true);
222 break;
223 case OP_NOTLIKE:
224 result = !MatchString(m_value.getString(), value->getAsString(row, col, _T("")), true);
225 break;
226 default:
227 break;
228 }
229 return result;
230 }
231
232 /**
233 * Condition group constructor
234 */
235 DCTableConditionGroup::DCTableConditionGroup()
236 {
237 m_conditions = new ObjectArray<DCTableCondition>(8, 8, true);
238 }
239
240 /**
241 * Condition group copy constructor
242 */
243 DCTableConditionGroup::DCTableConditionGroup(DCTableConditionGroup *src)
244 {
245 m_conditions = new ObjectArray<DCTableCondition>(src->m_conditions->size(), 8, true);
246 for(int i = 0; i < src->m_conditions->size(); i++)
247 m_conditions->add(new DCTableCondition(src->m_conditions->get(i)));
248 }
249
250 /**
251 * Create condition group from NXCP message
252 */
253 DCTableConditionGroup::DCTableConditionGroup(NXCPMessage *msg, UINT32 *baseId)
254 {
255 UINT32 varId = *baseId;
256 int count = msg->getFieldAsUInt32(varId++);
257 m_conditions = new ObjectArray<DCTableCondition>(count, 8, true);
258 for(int i = 0; i < count; i++)
259 {
260 TCHAR column[MAX_COLUMN_NAME], value[MAX_RESULT_LENGTH];
261 msg->getFieldAsString(varId++, column, MAX_COLUMN_NAME);
262 int op = (int)msg->getFieldAsUInt16(varId++);
263 msg->getFieldAsString(varId++, value, MAX_RESULT_LENGTH);
264 m_conditions->add(new DCTableCondition(column, op, value));
265 }
266 *baseId = varId;
267 }
268
269 /**
270 * Create condition group from NXMP record
271 */
272 DCTableConditionGroup::DCTableConditionGroup(ConfigEntry *e)
273 {
274 ConfigEntry *root = e->findEntry(_T("conditions"));
275 if (root != NULL)
276 {
277 ObjectArray<ConfigEntry> *conditions = root->getSubEntries(_T("condition#*"));
278 m_conditions = new ObjectArray<DCTableCondition>(conditions->size(), 4, true);
279 for(int i = 0; i < conditions->size(); i++)
280 {
281 ConfigEntry *c = conditions->get(i);
282 const TCHAR *column = c->getSubEntryValue(_T("column"), 0, _T(""));
283 const TCHAR *value = c->getSubEntryValue(_T("value"), 0, _T(""));
284 int op = c->getSubEntryValueAsInt(_T("operation"));
285 m_conditions->add(new DCTableCondition(column, op, value));
286 }
287 delete conditions;
288 }
289 else
290 {
291 m_conditions = new ObjectArray<DCTableCondition>(8, 8, true);
292 }
293 }
294
295 /**
296 * Condition group destructor
297 */
298 DCTableConditionGroup::~DCTableConditionGroup()
299 {
300 delete m_conditions;
301 }
302
303 /**
304 * Fill NXCP mesage
305 */
306 UINT32 DCTableConditionGroup::fillMessage(NXCPMessage *msg, UINT32 baseId)
307 {
308 UINT32 varId = baseId;
309 msg->setField(varId++, (UINT32)m_conditions->size());
310 for(int i = 0; i < m_conditions->size(); i++)
311 {
312 DCTableCondition *c = m_conditions->get(i);
313 msg->setField(varId++, c->getColumn());
314 msg->setField(varId++, (UINT16)c->getOperation());
315 msg->setField(varId++, c->getValue());
316 }
317 return varId;
318 }
319
320 /**
321 * Check if condition group is true
322 */
323 bool DCTableConditionGroup::check(Table *value, int row)
324 {
325 for(int i = 0; i < m_conditions->size(); i++)
326 if (!m_conditions->get(i)->check(value, row))
327 return false;
328 return true;
329 }
330
331 /**
332 * Table threshold constructor
333 */
334 DCTableThreshold::DCTableThreshold()
335 {
336 m_id = CreateUniqueId(IDG_THRESHOLD);
337 m_groups = new ObjectArray<DCTableConditionGroup>(4, 4, true);
338 m_activationEvent = EVENT_TABLE_THRESHOLD_ACTIVATED;
339 m_deactivationEvent = EVENT_TABLE_THRESHOLD_DEACTIVATED;
340 m_sampleCount = 1;
341 m_instances = new StringObjectMap<DCTableThresholdInstance>(true);
342 }
343
344 /**
345 * Table threshold copy constructor
346 */
347 DCTableThreshold::DCTableThreshold(DCTableThreshold *src)
348 {
349 m_id = CreateUniqueId(IDG_THRESHOLD);
350 m_groups = new ObjectArray<DCTableConditionGroup>(src->m_groups->size(), 4, true);
351 for(int i = 0; i < src->m_groups->size(); i++)
352 m_groups->add(new DCTableConditionGroup(src->m_groups->get(i)));
353 m_activationEvent = src->m_activationEvent;
354 m_deactivationEvent = src->m_deactivationEvent;
355 m_sampleCount = src->m_sampleCount;
356 m_instances = new StringObjectMap<DCTableThresholdInstance>(true);
357 }
358
359 /**
360 * Create table threshold from database
361 * Expected column order: id,activation_event,deactivation_event,sample_count
362 */
363 DCTableThreshold::DCTableThreshold(DB_HANDLE hdb, DB_RESULT hResult, int row)
364 {
365 m_id = DBGetFieldLong(hResult, row, 0);
366 m_activationEvent = DBGetFieldULong(hResult, row, 1);
367 m_deactivationEvent = DBGetFieldULong(hResult, row, 2);
368 m_groups = new ObjectArray<DCTableConditionGroup>(4, 4, true);
369 m_sampleCount = DBGetFieldLong(hResult, row, 3);
370 m_instances = new StringObjectMap<DCTableThresholdInstance>(true);
371 loadConditions(hdb);
372 loadInstances(hdb);
373 }
374
375 /**
376 * Create table threshold from NXCP message
377 */
378 DCTableThreshold::DCTableThreshold(NXCPMessage *msg, UINT32 *baseId)
379 {
380 UINT32 fieldId = *baseId;
381 m_id = msg->getFieldAsUInt32(fieldId++);
382 if (m_id == 0)
383 m_id = CreateUniqueId(IDG_THRESHOLD);
384 m_activationEvent = msg->getFieldAsUInt32(fieldId++);
385 m_deactivationEvent = msg->getFieldAsUInt32(fieldId++);
386 m_sampleCount = msg->getFieldAsUInt32(fieldId++);
387 int count = (int)msg->getFieldAsUInt32(fieldId++);
388 m_groups = new ObjectArray<DCTableConditionGroup>(count, 4, true);
389 *baseId = fieldId;
390 for(int i = 0; i < count; i++)
391 m_groups->add(new DCTableConditionGroup(msg, baseId));
392 m_instances = new StringObjectMap<DCTableThresholdInstance>(true);
393 }
394
395 /**
396 * Create from NXMP record
397 */
398 DCTableThreshold::DCTableThreshold(ConfigEntry *e)
399 {
400 m_id = CreateUniqueId(IDG_THRESHOLD);
401 m_activationEvent = EventCodeFromName(e->getSubEntryValue(_T("activationEvent"), 0, _T("SYS_TABLE_THRESHOLD_ACTIVATED")));
402 m_deactivationEvent = EventCodeFromName(e->getSubEntryValue(_T("deactivationEvent"), 0, _T("SYS_TABLE_THRESHOLD_DEACTIVATED")));
403 m_sampleCount = e->getSubEntryValueAsInt(_T("sampleCount"), 0, 1);
404
405 ConfigEntry *groupsRoot = e->findEntry(_T("groups"));
406 if (groupsRoot != NULL)
407 {
408 ObjectArray<ConfigEntry> *groups = groupsRoot->getSubEntries(_T("group#*"));
409 m_groups = new ObjectArray<DCTableConditionGroup>(groups->size(), 4, true);
410 for(int i = 0; i < groups->size(); i++)
411 {
412 m_groups->add(new DCTableConditionGroup(groups->get(i)));
413 }
414 delete groups;
415 }
416 else
417 {
418 m_groups = new ObjectArray<DCTableConditionGroup>(4, 4, true);
419 }
420 m_instances = new StringObjectMap<DCTableThresholdInstance>(true);
421 }
422
423 /**
424 * Load conditions from database
425 */
426 void DCTableThreshold::loadConditions(DB_HANDLE hdb)
427 {
428 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT group_id,column_name,check_operation,check_value FROM dct_threshold_conditions WHERE threshold_id=? ORDER BY group_id,sequence_number"));
429 if (hStmt == NULL)
430 return;
431
432 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
433 DB_RESULT hResult = DBSelectPrepared(hStmt);
434 if (hResult != NULL)
435 {
436 int count = DBGetNumRows(hResult);
437 if (count > 0)
438 {
439 DCTableConditionGroup *group = NULL;
440 int groupId = -1;
441 for(int i = 0; i < count; i++)
442 {
443 if ((DBGetFieldLong(hResult, i, 0) != groupId) || (group == NULL))
444 {
445 groupId = DBGetFieldLong(hResult, i, 0);
446 group = new DCTableConditionGroup();
447 m_groups->add(group);
448 }
449 TCHAR column[MAX_COLUMN_NAME], value[MAX_RESULT_LENGTH];
450 group->getConditions()->add(
451 new DCTableCondition(
452 DBGetField(hResult, i, 1, column, MAX_COLUMN_NAME),
453 DBGetFieldLong(hResult, i, 2),
454 DBGetField(hResult, i, 3, value, MAX_RESULT_LENGTH)));
455 }
456 }
457 DBFreeResult(hResult);
458 }
459 DBFreeStatement(hStmt);
460 }
461
462 /**
463 * Load instances from database
464 */
465 void DCTableThreshold::loadInstances(DB_HANDLE hdb)
466 {
467 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT instance,match_count,is_active FROM dct_threshold_instances WHERE threshold_id=?"));
468 if (hStmt == NULL)
469 return;
470
471 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
472 DB_RESULT hResult = DBSelectPrepared(hStmt);
473 if (hResult != NULL)
474 {
475 int count = DBGetNumRows(hResult);
476 for(int i = 0; i < count; i++)
477 {
478 TCHAR name[1024];
479 DBGetField(hResult, i, 0, name, 1024);
480 m_instances->set(name, new DCTableThresholdInstance(name, DBGetFieldLong(hResult, i, 1), DBGetFieldLong(hResult, i, 2) ? true : false));
481 }
482 DBFreeResult(hResult);
483 }
484 DBFreeStatement(hStmt);
485 }
486
487 /**
488 * Table threshold destructor
489 */
490 DCTableThreshold::~DCTableThreshold()
491 {
492 delete m_groups;
493 delete m_instances;
494 }
495
496 /**
497 * Enumeration callback for saving threshold instances
498 */
499 static EnumerationCallbackResult SaveThresholdInstancesCallback(const TCHAR *key, const void *value, void *arg)
500 {
501 DB_STATEMENT hStmt = (DB_STATEMENT)arg;
502 const DCTableThresholdInstance *i = (const DCTableThresholdInstance *)value;
503 DBBind(hStmt, 2, DB_SQLTYPE_VARCHAR, i->getName(), DB_BIND_STATIC);
504 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, i->getMatchCount());
505 DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, i->isActive() ? _T("1") : _T("0"), DB_BIND_STATIC);
506 DBExecute(hStmt);
507 return _CONTINUE;
508 }
509
510 /**
511 * Save threshold to database
512 */
513 bool DCTableThreshold::saveToDatabase(DB_HANDLE hdb, UINT32 tableId, int seq)
514 {
515 DB_STATEMENT hStmt = DBPrepare(hdb, _T("INSERT INTO dct_thresholds (id,table_id,sequence_number,activation_event,deactivation_event,sample_count) VALUES (?,?,?,?,?,?)"));
516 if (hStmt == NULL)
517 return false;
518
519 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
520 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, tableId);
521 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, (INT32)seq);
522 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, m_activationEvent);
523 DBBind(hStmt, 5, DB_SQLTYPE_INTEGER, m_deactivationEvent);
524 DBBind(hStmt, 6, DB_SQLTYPE_INTEGER, m_sampleCount);
525 DBExecute(hStmt);
526 DBFreeStatement(hStmt);
527
528 if (m_groups->size() > 0)
529 {
530 hStmt = DBPrepare(hdb, _T("INSERT INTO dct_threshold_conditions (threshold_id,group_id,sequence_number,column_name,check_operation,check_value) VALUES (?,?,?,?,?,?)"));
531 if (hStmt == NULL)
532 return false;
533 for(int i = 0; i < m_groups->size(); i++)
534 {
535 DCTableConditionGroup *group = m_groups->get(i);
536 ObjectArray<DCTableCondition> *conditions = group->getConditions();
537 for(int j = 0; j < conditions->size(); j++)
538 {
539 DCTableCondition *c = conditions->get(j);
540 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
541 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, (INT32)i);
542 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, (INT32)j);
543 DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, c->getColumn(), DB_BIND_STATIC);
544 DBBind(hStmt, 5, DB_SQLTYPE_INTEGER, (INT32)c->getOperation());
545 DBBind(hStmt, 6, DB_SQLTYPE_VARCHAR, c->getValue(), DB_BIND_STATIC);
546 DBExecute(hStmt);
547 }
548 }
549 DBFreeStatement(hStmt);
550 }
551
552 if (m_instances->size() > 0)
553 {
554 hStmt = DBPrepare(hdb, _T("INSERT INTO dct_threshold_instances (threshold_id,instance,match_count,is_active) VALUES (?,?,?,?)"));
555 if (hStmt == NULL)
556 return false;
557
558 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
559 m_instances->forEach(SaveThresholdInstancesCallback, hStmt);
560 DBFreeStatement(hStmt);
561 }
562 return true;
563 }
564
565 /**
566 * Fill NXCP message with threshold data
567 */
568 UINT32 DCTableThreshold::fillMessage(NXCPMessage *msg, UINT32 baseId)
569 {
570 UINT32 fieldId = baseId;
571 msg->setField(fieldId++, m_id);
572 msg->setField(fieldId++, m_activationEvent);
573 msg->setField(fieldId++, m_deactivationEvent);
574 msg->setField(fieldId++, m_sampleCount);
575 msg->setField(fieldId++, (UINT32)m_groups->size());
576 for(int i = 0; i < m_groups->size(); i++)
577 {
578 fieldId = m_groups->get(i)->fillMessage(msg, fieldId);
579 }
580 return fieldId;
581 }
582
583 /**
584 * Check threshold
585 * Method will return the following codes:
586 * ACTIVATED - when value match the threshold condition while previous check doesn't
587 * DEACTIVATED - when value doesn't match the threshold condition while previous check do
588 * ALREADY_ACTIVE - when value match the threshold condition and threshold is already active
589 * ALREADY_INACTIVE - when value doesn't match the threshold condition and threshold is already inactive
590 */
591 ThresholdCheckResult DCTableThreshold::check(Table *value, int row, const TCHAR *instance)
592 {
593 for(int i = 0; i < m_groups->size(); i++)
594 {
595 if (m_groups->get(i)->check(value, row))
596 {
597 DCTableThresholdInstance *i = m_instances->get(instance);
598 if (i != NULL)
599 {
600 i->incMatchCount();
601 if (i->isActive())
602 return ALREADY_ACTIVE;
603 }
604 else
605 {
606 i = new DCTableThresholdInstance(instance, 1, false);
607 m_instances->set(instance, i);
608 }
609 if (i->getMatchCount() >= m_sampleCount)
610 {
611 i->setActive();
612 return ACTIVATED;
613 }
614 return ALREADY_INACTIVE;
615 }
616 }
617
618 // no match
619 DCTableThresholdInstance *i = m_instances->get(instance);
620 if (i != NULL)
621 {
622 bool deactivated = i->isActive();
623 m_instances->remove(instance);
624 return deactivated ? DEACTIVATED : ALREADY_INACTIVE;
625 }
626 return ALREADY_INACTIVE;
627 }
628
629 /**
630 * Create NXMP record for threshold
631 */
632 void DCTableThreshold::createNXMPRecord(String &str, int id)
633 {
634 TCHAR activationEvent[MAX_EVENT_NAME], deactivationEvent[MAX_EVENT_NAME];
635
636 EventNameFromCode(m_activationEvent, activationEvent);
637 EventNameFromCode(m_deactivationEvent, deactivationEvent);
638 str.appendFormattedString(_T("\t\t\t\t\t\t<threshold id=\"%d\">\n")
639 _T("\t\t\t\t\t\t\t<activationEvent>%s</activationEvent>\n")
640 _T("\t\t\t\t\t\t\t<deactivationEvent>%s</deactivationEvent>\n")
641 _T("\t\t\t\t\t\t\t<sampleCount>%s</sampleCount>\n")
642 _T("\t\t\t\t\t\t\t<groups>\n"),
643 id, (const TCHAR *)EscapeStringForXML2(activationEvent),
644 (const TCHAR *)EscapeStringForXML2(deactivationEvent),
645 m_sampleCount);
646 for(int i = 0; i < m_groups->size(); i++)
647 {
648 str.appendFormattedString(_T("\t\t\t\t\t\t\t\t<group id=\"%d\">\n\t\t\t\t\t\t\t\t\t<conditions>\n"), i + 1);
649 ObjectArray<DCTableCondition> *conditions = m_groups->get(i)->getConditions();
650 for(int j = 0; j < conditions->size(); j++)
651 {
652 DCTableCondition *c = conditions->get(j);
653 str.appendFormattedString(_T("\t\t\t\t\t\t\t\t\t\t<condition id=\"%d\">\n")
654 _T("\t\t\t\t\t\t\t\t\t\t\t<column>%s</column>\n")
655 _T("\t\t\t\t\t\t\t\t\t\t\t<operation>%d</operation>\n")
656 _T("\t\t\t\t\t\t\t\t\t\t\t<value>%s</value>\n")
657 _T("\t\t\t\t\t\t\t\t\t\t</condition>\n"),
658 j + 1, (const TCHAR *)EscapeStringForXML2(c->getColumn()),
659 c->getOperation(), (const TCHAR *)EscapeStringForXML2(c->getValue()));
660 }
661 str += _T("\t\t\t\t\t\t\t\t\t</conditions>\n\t\t\t\t\t\t\t\t</group>\n");
662 }
663 str += _T("\t\t\t\t\t\t\t</groups>\n\t\t\t\t\t\t</threshold>\n");
664 }
665
666 /**
667 * Callback for cloning threshld instances
668 */
669 static EnumerationCallbackResult CloneThresholdInstances(const TCHAR *key, const void *value, void *data)
670 {
671 ((StringObjectMap<DCTableThresholdInstance> *)data)->set(key, new DCTableThresholdInstance((const DCTableThresholdInstance *)value));
672 return _CONTINUE;
673 }
674
675 /**
676 * Copy threshold state
677 */
678 void DCTableThreshold::copyState(DCTableThreshold *src)
679 {
680 m_instances->clear();
681 src->m_instances->forEach(CloneThresholdInstances, m_instances);
682 }