implemented new tdata format
[public/netxms.git] / src / server / core / dctable.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 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: dctable.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 /**
26 * Column ID cache
27 */
28 TC_ID_MAP_ENTRY *DCTable::m_cache = NULL;
29 int DCTable::m_cacheSize = 0;
30 int DCTable::m_cacheAllocated = 0;
31 MUTEX DCTable::m_cacheMutex = MutexCreate();
32
33 /**
34 * Compare cache element's name to string key
35 */
36 static int CompareCacheElements(const void *key, const void *element)
37 {
38 return _tcsicmp((const TCHAR *)key, ((TC_ID_MAP_ENTRY *)element)->name);
39 }
40
41 /**
42 * Compare names of two cache elements
43 */
44 static int CompareCacheElements2(const void *e1, const void *e2)
45 {
46 return _tcsicmp(((TC_ID_MAP_ENTRY *)e1)->name, ((TC_ID_MAP_ENTRY *)e2)->name);
47 }
48
49 /**
50 * Get column ID from column name
51 */
52 INT32 DCTable::columnIdFromName(const TCHAR *name)
53 {
54 TC_ID_MAP_ENTRY buffer;
55
56 // check that column name is valid
57 if ((name == NULL) || (*name == 0))
58 return 0;
59
60 MutexLock(m_cacheMutex);
61
62 TC_ID_MAP_ENTRY *entry = (TC_ID_MAP_ENTRY *)bsearch(name, m_cache, m_cacheSize, sizeof(TC_ID_MAP_ENTRY), CompareCacheElements);
63 if (entry == NULL)
64 {
65 // Not in cache, go to database
66 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
67
68 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT column_id FROM dct_column_names WHERE column_name=?"));
69 if (hStmt != NULL)
70 {
71 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, name, DB_BIND_STATIC);
72 DB_RESULT hResult = DBSelectPrepared(hStmt);
73 if (hResult != NULL)
74 {
75 entry = &buffer;
76 nx_strncpy(entry->name, name, MAX_COLUMN_NAME);
77 if (DBGetNumRows(hResult) > 0)
78 {
79 // found in database
80 entry->id = DBGetFieldLong(hResult, 0, 0);
81 }
82 else
83 {
84 // no such column name in database
85 entry->id = CreateUniqueId(IDG_DCT_COLUMN);
86
87 // update database
88 DB_STATEMENT hStmt2 = DBPrepare(hdb, _T("INSERT INTO dct_column_names (column_id,column_name) VALUES (?,?)"));
89 if (hStmt2 != NULL)
90 {
91 DBBind(hStmt2, 1, DB_SQLTYPE_INTEGER, entry->id);
92 DBBind(hStmt2, 2, DB_SQLTYPE_VARCHAR, name, DB_BIND_STATIC);
93 DBExecute(hStmt2);
94 DBFreeStatement(hStmt2);
95 }
96 }
97
98 DBFreeResult(hResult);
99
100 // Add to cache
101 if (m_cacheSize == m_cacheAllocated)
102 {
103 m_cacheAllocated += 16;
104 m_cache = (TC_ID_MAP_ENTRY *)realloc(m_cache, sizeof(TC_ID_MAP_ENTRY) * m_cacheAllocated);
105 }
106 memcpy(&m_cache[m_cacheSize++], entry, sizeof(TC_ID_MAP_ENTRY));
107 qsort(m_cache, m_cacheSize, sizeof(TC_ID_MAP_ENTRY), CompareCacheElements2);
108
109 DbgPrintf(6, _T("DCTable::columnIdFromName(): column name %s added to cache, ID=%d"), name, (int)entry->id);
110 }
111 DBFreeStatement(hStmt);
112 }
113
114 DBConnectionPoolReleaseConnection(hdb);
115 }
116
117 MutexUnlock(m_cacheMutex);
118 return (entry != NULL) ? entry->id : 0;
119 }
120
121 /**
122 * Default constructor
123 */
124 DCTable::DCTable() : DCObject()
125 {
126 m_columns = new ObjectArray<DCTableColumn>(8, 8, true);
127 m_thresholds = new ObjectArray<DCTableThreshold>(0, 4, true);
128 m_lastValue = NULL;
129 }
130
131 /**
132 * Copy constructor
133 */
134 DCTable::DCTable(const DCTable *src) : DCObject(src)
135 {
136 m_columns = new ObjectArray<DCTableColumn>(src->m_columns->size(), 8, true);
137 for(int i = 0; i < src->m_columns->size(); i++)
138 m_columns->add(new DCTableColumn(src->m_columns->get(i)));
139 m_thresholds = new ObjectArray<DCTableThreshold>(src->m_thresholds->size(), 4, true);
140 for(int i = 0; i < src->m_thresholds->size(); i++)
141 m_thresholds->add(new DCTableThreshold(src->m_thresholds->get(i)));
142 m_lastValue = NULL;
143 }
144
145 /**
146 * Constructor for creating new DCTable from scratch
147 */
148 DCTable::DCTable(UINT32 id, const TCHAR *name, int source, int pollingInterval, int retentionTime,
149 Template *node, const TCHAR *description, const TCHAR *systemTag)
150 : DCObject(id, name, source, pollingInterval, retentionTime, node, description, systemTag)
151 {
152 m_columns = new ObjectArray<DCTableColumn>(8, 8, true);
153 m_thresholds = new ObjectArray<DCTableThreshold>(0, 4, true);
154 m_lastValue = NULL;
155 }
156
157 /**
158 * Constructor for creating DCTable from database
159 * Assumes that fields in SELECT query are in following order:
160 * item_id,template_id,template_item_id,name,
161 * description,flags,source,snmp_port,polling_interval,retention_time,
162 * status,system_tag,resource_id,proxy_node,perftab_settings,
163 * transformation_script,comments,guid
164 */
165 DCTable::DCTable(DB_HANDLE hdb, DB_RESULT hResult, int iRow, Template *pNode) : DCObject()
166 {
167 m_id = DBGetFieldULong(hResult, iRow, 0);
168 m_dwTemplateId = DBGetFieldULong(hResult, iRow, 1);
169 m_dwTemplateItemId = DBGetFieldULong(hResult, iRow, 2);
170 DBGetField(hResult, iRow, 3, m_name, MAX_ITEM_NAME);
171 DBGetField(hResult, iRow, 4, m_description, MAX_DB_STRING);
172 m_flags = (WORD)DBGetFieldLong(hResult, iRow, 5);
173 m_source = (BYTE)DBGetFieldLong(hResult, iRow, 6);
174 m_snmpPort = (WORD)DBGetFieldLong(hResult, iRow, 7);
175 m_iPollingInterval = DBGetFieldLong(hResult, iRow, 8);
176 m_iRetentionTime = DBGetFieldLong(hResult, iRow, 9);
177 m_status = (BYTE)DBGetFieldLong(hResult, iRow, 10);
178 DBGetField(hResult, iRow, 11, m_systemTag, MAX_DB_STRING);
179 m_dwResourceId = DBGetFieldULong(hResult, iRow, 12);
180 m_sourceNode = DBGetFieldULong(hResult, iRow, 13);
181 m_pszPerfTabSettings = DBGetField(hResult, iRow, 14, NULL, 0);
182 TCHAR *pszTmp = DBGetField(hResult, iRow, 15, NULL, 0);
183 m_comments = DBGetField(hResult, iRow, 16, NULL, 0);
184 m_guid = DBGetFieldGUID(hResult, iRow, 17);
185 setTransformationScript(pszTmp);
186 free(pszTmp);
187
188 m_owner = pNode;
189 m_columns = new ObjectArray<DCTableColumn>(8, 8, true);
190 m_lastValue = NULL;
191
192 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT column_name,flags,snmp_oid,display_name FROM dc_table_columns WHERE table_id=? ORDER BY sequence_number"));
193 if (hStmt != NULL)
194 {
195 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
196 DB_RESULT hColumnList = DBSelectPrepared(hStmt);
197 if (hColumnList != NULL)
198 {
199 int count = DBGetNumRows(hColumnList);
200 for(int i = 0; i < count; i++)
201 m_columns->add(new DCTableColumn(hColumnList, i));
202 DBFreeResult(hColumnList);
203 }
204 DBFreeStatement(hStmt);
205 }
206
207 loadCustomSchedules(hdb);
208
209 m_thresholds = new ObjectArray<DCTableThreshold>(0, 4, true);
210 loadThresholds(hdb);
211 }
212
213 /**
214 * Create DCTable from import file
215 */
216 DCTable::DCTable(ConfigEntry *config, Template *owner) : DCObject(config, owner)
217 {
218 ConfigEntry *columnsRoot = config->findEntry(_T("columns"));
219 if (columnsRoot != NULL)
220 {
221 ObjectArray<ConfigEntry> *columns = columnsRoot->getSubEntries(_T("column#*"));
222 m_columns = new ObjectArray<DCTableColumn>(columns->size(), 8, true);
223 for(int i = 0; i < columns->size(); i++)
224 {
225 m_columns->add(new DCTableColumn(columns->get(i)));
226 }
227 delete columns;
228 }
229 else
230 {
231 m_columns = new ObjectArray<DCTableColumn>(8, 8, true);
232 }
233
234 ConfigEntry *thresholdsRoot = config->findEntry(_T("thresholds"));
235 if (thresholdsRoot != NULL)
236 {
237 ObjectArray<ConfigEntry> *thresholds = thresholdsRoot->getSubEntries(_T("threshold#*"));
238 m_thresholds = new ObjectArray<DCTableThreshold>(thresholds->size(), 8, true);
239 for(int i = 0; i < thresholds->size(); i++)
240 {
241 m_thresholds->add(new DCTableThreshold(thresholds->get(i)));
242 }
243 delete thresholds;
244 }
245 else
246 {
247 m_thresholds = new ObjectArray<DCTableThreshold>(0, 4, true);
248 }
249
250 m_lastValue = NULL;
251 }
252
253 /**
254 * Destructor
255 */
256 DCTable::~DCTable()
257 {
258 delete m_columns;
259 delete m_thresholds;
260 if (m_lastValue != NULL)
261 m_lastValue->decRefCount();
262 }
263
264 /**
265 * Clean expired data
266 */
267 void DCTable::deleteExpiredData()
268 {
269 TCHAR query[256];
270 time_t now;
271
272 now = time(NULL);
273
274 lock();
275 _sntprintf(query, 256, _T("DELETE FROM tdata_%d WHERE (item_id=%d) AND (tdata_timestamp<%ld)"),
276 (int)m_owner->getId(), (int)m_id, (long)(now - (time_t)m_iRetentionTime * 86400));
277 unlock();
278
279 QueueSQLRequest(query);
280 }
281
282 /**
283 * Delete all collected data
284 */
285 bool DCTable::deleteAllData()
286 {
287 TCHAR szQuery[256];
288 bool success;
289
290 lock();
291 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
292 _sntprintf(szQuery, 256, _T("DELETE FROM tdata_%d WHERE item_id=%d"), m_owner->getId(), (int)m_id);
293 success = DBQuery(hdb, szQuery) ? true : false;
294 DBConnectionPoolReleaseConnection(hdb);
295 unlock();
296 return success;
297 }
298
299 /**
300 * Process new collected value. Should return true on success.
301 * If returns false, current poll result will be converted into data collection error.
302 *
303 * @return true on success
304 */
305 bool DCTable::processNewValue(time_t timestamp, const void *value, bool *updateStatus)
306 {
307 *updateStatus = false;
308 lock();
309
310 // Normally m_owner shouldn't be NULL for polled items, but who knows...
311 if (m_owner == NULL)
312 {
313 unlock();
314 return false;
315 }
316
317 // Transform input value
318 // Cluster can have only aggregated data, and transformation
319 // should not be used on aggregation
320 if ((m_owner->getObjectClass() != OBJECT_CLUSTER) || (m_flags & DCF_TRANSFORM_AGGREGATED))
321 {
322 if (!transform((Table *)value))
323 {
324 unlock();
325 ((Table *)value)->decRefCount();
326 return false;
327 }
328 }
329
330 m_dwErrorCount = 0;
331 if (m_lastValue != NULL)
332 m_lastValue->decRefCount();
333 m_lastValue = (Table *)value;
334 m_lastValue->setTitle(m_description);
335 m_lastValue->setSource(m_source);
336
337 // Copy required fields into local variables
338 UINT32 tableId = m_id;
339 UINT32 nodeId = m_owner->getId();
340 bool save = (m_flags & DCF_NO_STORAGE) == 0;
341
342 ((Table *)value)->incRefCount();
343
344 unlock();
345
346 // Save data to database
347 // Object is unlocked, so only local variables can be used
348 if (save)
349 {
350 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
351 if (!DBBegin(hdb))
352 {
353 DBConnectionPoolReleaseConnection(hdb);
354 return true;
355 }
356
357 INT64 recordId = ((INT64)timestamp << 30) | (((INT64)tableId & 0xFFFF) << 14);
358 BOOL success = FALSE;
359 Table *data = (Table *)value;
360
361 TCHAR query[256];
362 _sntprintf(query, 256, _T("INSERT INTO tdata_%d (item_id,tdata_timestamp,tdata_value) VALUES (?,?,?)"), (int)nodeId);
363 DB_STATEMENT hStmt = DBPrepare(hdb, query);
364 if (hStmt != NULL)
365 {
366 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, tableId);
367 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, (INT32)timestamp);
368 DBBind(hStmt, 3, DB_SQLTYPE_TEXT, DB_CTYPE_UTF8_STRING, data->createPackedXML(), DB_BIND_DYNAMIC);
369 success = DBExecute(hStmt);
370 DBFreeStatement(hStmt);
371 }
372
373 if (success)
374 DBCommit(hdb);
375 else
376 DBRollback(hdb);
377
378 DBConnectionPoolReleaseConnection(hdb);
379 }
380 if ((g_offlineDataRelevanceTime <= 0) || (timestamp > (time(NULL) - g_offlineDataRelevanceTime)))
381 checkThresholds((Table *)value);
382
383 if (g_flags & AF_PERFDATA_STORAGE_DRIVER_LOADED)
384 PerfDataStorageRequest(this, timestamp, (Table *)value);
385
386 ((Table *)value)->decRefCount();
387 return true;
388 }
389
390 /**
391 * Transform received value
392 */
393 bool DCTable::transform(Table *value)
394 {
395 if (m_transformationScript == NULL)
396 return true;
397
398 bool success = false;
399 NXSL_VM *vm = new NXSL_VM(new NXSL_ServerEnv());
400 if (vm->load(m_transformationScript))
401 {
402 NXSL_Value *nxslValue = new NXSL_Value(new NXSL_Object(&g_nxslStaticTableClass, value));
403 vm->setGlobalVariable(_T("$object"), m_owner->createNXSLObject());
404 if (m_owner->getObjectClass() == OBJECT_NODE)
405 {
406 vm->setGlobalVariable(_T("$node"), m_owner->createNXSLObject());
407 }
408 vm->setGlobalVariable(_T("$dci"), createNXSLObject());
409 vm->setGlobalVariable(_T("$isCluster"), new NXSL_Value((m_owner->getObjectClass() == OBJECT_CLUSTER) ? 1 : 0));
410
411 // remove lock from DCI for script execution to avoid deadlocks
412 unlock();
413 success = vm->run(1, &nxslValue);
414 lock();
415 if (!success)
416 {
417 if (vm->getErrorCode() == NXSL_ERR_EXECUTION_ABORTED)
418 {
419 DbgPrintf(6, _T("Transformation script for DCI \"%s\" [%d] on node %s [%d] aborted"),
420 m_description, m_id, getOwnerName(), getOwnerId());
421 }
422 else
423 {
424 TCHAR buffer[1024];
425 _sntprintf(buffer, 1024, _T("DCI::%s::%d::TransformationScript"), getOwnerName(), m_id);
426 PostDciEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, m_id, "ssd", buffer, vm->getErrorText(), m_id);
427 nxlog_write(MSG_TRANSFORMATION_SCRIPT_EXECUTION_ERROR, NXLOG_WARNING, "dsdss",
428 getOwnerId(), getOwnerName(), m_id, m_name, vm->getErrorText());
429 }
430 }
431 }
432 else
433 {
434 TCHAR buffer[1024];
435 _sntprintf(buffer, 1024, _T("DCI::%s::%d::TransformationScript"), getOwnerName(), m_id);
436 PostDciEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, m_id, "ssd", buffer, vm->getErrorText(), m_id);
437 nxlog_write(MSG_TRANSFORMATION_SCRIPT_EXECUTION_ERROR, NXLOG_WARNING, "dsdss",
438 getOwnerId(), getOwnerName(), m_id, m_name, vm->getErrorText());
439 }
440 delete vm;
441 return success;
442 }
443
444 /**
445 * Check thresholds
446 */
447 void DCTable::checkThresholds(Table *value)
448 {
449 static const TCHAR *paramNames[] = { _T("dciName"), _T("dciDescription"), _T("dciId"), _T("row"), _T("instance") };
450
451 lock();
452 for(int row = 0; row < value->getNumRows(); row++)
453 {
454 TCHAR instance[MAX_RESULT_LENGTH];
455 value->buildInstanceString(row, instance, MAX_RESULT_LENGTH);
456 for(int i = 0; i < m_thresholds->size(); i++)
457 {
458 DCTableThreshold *t = m_thresholds->get(i);
459 ThresholdCheckResult result = t->check(value, row, instance);
460 switch(result)
461 {
462 case ACTIVATED:
463 PostDciEventWithNames(t->getActivationEvent(), m_owner->getId(), m_id, "ssids", paramNames, m_name, m_description, m_id, row, instance);
464 if (!(m_flags & DCF_ALL_THRESHOLDS))
465 i = m_thresholds->size(); // Stop processing (for current row)
466 break;
467 case DEACTIVATED:
468 PostDciEventWithNames(t->getDeactivationEvent(), m_owner->getId(), m_id, "ssids", paramNames, m_name, m_description, m_id, row, instance);
469 break;
470 case ALREADY_ACTIVE:
471 i = m_thresholds->size(); // Threshold condition still true, stop processing
472 break;
473 default:
474 break;
475 }
476 }
477 }
478 unlock();
479 }
480
481 /**
482 * Process new data collection error
483 */
484 void DCTable::processNewError(bool noInstance, time_t now)
485 {
486 m_dwErrorCount++;
487 }
488
489 /**
490 * Save to database
491 */
492 bool DCTable::saveToDatabase(DB_HANDLE hdb)
493 {
494 DB_STATEMENT hStmt;
495 if (IsDatabaseRecordExist(hdb, _T("dc_tables"), _T("item_id"), m_id))
496 {
497 hStmt = DBPrepare(hdb, _T("UPDATE dc_tables SET node_id=?,template_id=?,template_item_id=?,name=?,")
498 _T("description=?,flags=?,source=?,snmp_port=?,polling_interval=?,")
499 _T("retention_time=?,status=?,system_tag=?,resource_id=?,proxy_node=?,")
500 _T("perftab_settings=?,transformation_script=?,comments=?,guid=? WHERE item_id=?"));
501 }
502 else
503 {
504 hStmt = DBPrepare(hdb, _T("INSERT INTO dc_tables (node_id,template_id,template_item_id,name,")
505 _T("description,flags,source,snmp_port,polling_interval,")
506 _T("retention_time,status,system_tag,resource_id,proxy_node,perftab_settings,")
507 _T("transformation_script,comments,guid,item_id) ")
508 _T("VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"));
509 }
510 if (hStmt == NULL)
511 return FALSE;
512
513 lock();
514
515 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, (m_owner == NULL) ? (UINT32)0 : m_owner->getId());
516 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, m_dwTemplateId);
517 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, m_dwTemplateItemId);
518 DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, m_name, DB_BIND_STATIC);
519 DBBind(hStmt, 5, DB_SQLTYPE_VARCHAR, m_description, DB_BIND_STATIC);
520 DBBind(hStmt, 6, DB_SQLTYPE_INTEGER, (UINT32)m_flags);
521 DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, (INT32)m_source);
522 DBBind(hStmt, 8, DB_SQLTYPE_INTEGER, (UINT32)m_snmpPort);
523 DBBind(hStmt, 9, DB_SQLTYPE_INTEGER, (INT32)m_iPollingInterval);
524 DBBind(hStmt, 10, DB_SQLTYPE_INTEGER, (INT32)m_iRetentionTime);
525 DBBind(hStmt, 11, DB_SQLTYPE_INTEGER, (INT32)m_status);
526 DBBind(hStmt, 12, DB_SQLTYPE_VARCHAR, m_systemTag, DB_BIND_STATIC);
527 DBBind(hStmt, 13, DB_SQLTYPE_INTEGER, m_dwResourceId);
528 DBBind(hStmt, 14, DB_SQLTYPE_INTEGER, m_sourceNode);
529 DBBind(hStmt, 15, DB_SQLTYPE_TEXT, m_pszPerfTabSettings, DB_BIND_STATIC);
530 DBBind(hStmt, 16, DB_SQLTYPE_TEXT, m_transformationScriptSource, DB_BIND_STATIC);
531 DBBind(hStmt, 17, DB_SQLTYPE_TEXT, m_comments, DB_BIND_STATIC);
532 DBBind(hStmt, 18, DB_SQLTYPE_VARCHAR, m_guid);
533 DBBind(hStmt, 19, DB_SQLTYPE_INTEGER, m_id);
534
535 bool result = DBExecute(hStmt);
536 DBFreeStatement(hStmt);
537
538 if (result)
539 {
540 // Save column configuration
541 hStmt = DBPrepare(hdb, _T("DELETE FROM dc_table_columns WHERE table_id=?"));
542 if (hStmt != NULL)
543 {
544 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
545 result = DBExecute(hStmt);
546 DBFreeStatement(hStmt);
547 }
548 else
549 {
550 result = false;
551 }
552
553 if (result && (m_columns->size() > 0))
554 {
555 hStmt = DBPrepare(hdb, _T("INSERT INTO dc_table_columns (table_id,sequence_number,column_name,snmp_oid,flags,display_name) VALUES (?,?,?,?,?,?)"));
556 if (hStmt != NULL)
557 {
558 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
559 for(int i = 0; i < m_columns->size(); i++)
560 {
561 DCTableColumn *column = m_columns->get(i);
562 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, (INT32)(i + 1));
563 DBBind(hStmt, 3, DB_SQLTYPE_VARCHAR, column->getName(), DB_BIND_STATIC);
564 SNMP_ObjectId *oid = column->getSnmpOid();
565 DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, (oid != NULL) ? (const TCHAR *)oid->toString() : NULL, DB_BIND_TRANSIENT);
566 DBBind(hStmt, 5, DB_SQLTYPE_INTEGER, (INT32)column->getFlags());
567 DBBind(hStmt, 6, DB_SQLTYPE_VARCHAR, column->getDisplayName(), DB_BIND_STATIC);
568
569 result = DBExecute(hStmt);
570 if (!result)
571 break;
572 }
573 DBFreeStatement(hStmt);
574 }
575 else
576 {
577 result = false;
578 }
579 }
580 }
581
582 saveThresholds(hdb);
583
584 unlock();
585 return result ? DCObject::saveToDatabase(hdb) : false;
586 }
587
588 /**
589 * Load thresholds from database
590 */
591 bool DCTable::loadThresholds(DB_HANDLE hdb)
592 {
593 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT id,activation_event,deactivation_event FROM dct_thresholds WHERE table_id=? ORDER BY sequence_number"));
594 if (hStmt == NULL)
595 return false;
596
597 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
598 DB_RESULT hResult = DBSelectPrepared(hStmt);
599 if (hResult != NULL)
600 {
601 int count = DBGetNumRows(hResult);
602 for(int i = 0; i < count; i++)
603 {
604 DCTableThreshold *t = new DCTableThreshold(hdb, hResult, i);
605 m_thresholds->add(t);
606 }
607 DBFreeResult(hResult);
608 }
609 DBFreeStatement(hStmt);
610 return true;
611 }
612
613 /**
614 * Save thresholds to database
615 */
616 bool DCTable::saveThresholds(DB_HANDLE hdb)
617 {
618 DB_STATEMENT hStmt = DBPrepare(hdb, _T("DELETE FROM dct_threshold_conditions WHERE threshold_id=?"));
619 if (hStmt == NULL)
620 return false;
621
622 for(int i = 0; i < m_thresholds->size(); i++)
623 {
624 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_thresholds->get(i)->getId());
625 DBExecute(hStmt);
626 }
627 DBFreeStatement(hStmt);
628
629 hStmt = DBPrepare(hdb, _T("DELETE FROM dct_thresholds WHERE table_id=?"));
630 if (hStmt == NULL)
631 return false;
632 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
633 DBExecute(hStmt);
634 DBFreeStatement(hStmt);
635
636 for(int i = 0; i < m_thresholds->size(); i++)
637 m_thresholds->get(i)->saveToDatabase(hdb, m_id, i);
638 return true;
639 }
640
641 /**
642 * Delete table object and collected data from database
643 */
644 void DCTable::deleteFromDatabase()
645 {
646 TCHAR szQuery[256];
647
648 DCObject::deleteFromDatabase();
649
650 if(m_owner->getObjectClass() != OBJECT_TEMPLATE)
651 {
652 _sntprintf(szQuery, sizeof(szQuery) / sizeof(TCHAR), _T("DELETE FROM tdata_%d WHERE item_id=%d"), m_owner->getId(), (int)m_id);
653 QueueSQLRequest(szQuery);
654 }
655
656 _sntprintf(szQuery, sizeof(szQuery) / sizeof(TCHAR), _T("DELETE FROM dc_tables WHERE item_id=%d"), (int)m_id);
657 QueueSQLRequest(szQuery);
658 _sntprintf(szQuery, sizeof(szQuery) / sizeof(TCHAR), _T("DELETE FROM dc_table_columns WHERE table_id=%d"), (int)m_id);
659 QueueSQLRequest(szQuery);
660
661 for(int i = 0; i < m_thresholds->size(); i++)
662 {
663 _sntprintf(szQuery, 256, _T("DELETE FROM dct_threshold_conditions WHERE threshold_id=%d"), (int)m_thresholds->get(i)->getId());
664 QueueSQLRequest(szQuery);
665 }
666
667 _sntprintf(szQuery, sizeof(szQuery) / sizeof(TCHAR), _T("DELETE FROM dct_thresholds WHERE table_id=%d"), (int)m_id);
668 QueueSQLRequest(szQuery);
669 }
670
671 /**
672 * Create NXCP message with item data
673 */
674 void DCTable::createMessage(NXCPMessage *pMsg)
675 {
676 DCObject::createMessage(pMsg);
677
678 lock();
679 pMsg->setField(VID_NUM_COLUMNS, (UINT32)m_columns->size());
680 UINT32 varId = VID_DCI_COLUMN_BASE;
681 for(int i = 0; i < m_columns->size(); i++)
682 {
683 DCTableColumn *column = m_columns->get(i);
684 pMsg->setField(varId++, column->getName());
685 pMsg->setField(varId++, column->getFlags());
686 SNMP_ObjectId *oid = column->getSnmpOid();
687 if (oid != NULL)
688 pMsg->setFieldFromInt32Array(varId++, (UINT32)oid->length(), oid->value());
689 else
690 varId++;
691 pMsg->setField(varId++, column->getDisplayName());
692 varId += 6;
693 }
694
695 pMsg->setField(VID_NUM_THRESHOLDS, (UINT32)m_thresholds->size());
696 varId = VID_DCI_THRESHOLD_BASE;
697 for(int i = 0; i < m_thresholds->size(); i++)
698 {
699 varId = m_thresholds->get(i)->fillMessage(pMsg, varId);
700 }
701
702 unlock();
703 }
704
705 /**
706 * Update data collection object from NXCP message
707 */
708 void DCTable::updateFromMessage(NXCPMessage *pMsg)
709 {
710 DCObject::updateFromMessage(pMsg);
711
712 lock();
713
714 m_columns->clear();
715 int count = (int)pMsg->getFieldAsUInt32(VID_NUM_COLUMNS);
716 UINT32 varId = VID_DCI_COLUMN_BASE;
717 for(int i = 0; i < count; i++)
718 {
719 m_columns->add(new DCTableColumn(pMsg, varId));
720 varId += 10;
721 }
722
723 count = (int)pMsg->getFieldAsUInt32(VID_NUM_THRESHOLDS);
724 ObjectArray<DCTableThreshold> *newThresholds = new ObjectArray<DCTableThreshold>(count, 8, true);
725 varId = VID_DCI_THRESHOLD_BASE;
726 for(int i = 0; i < count; i++)
727 {
728 DCTableThreshold *t = new DCTableThreshold(pMsg, &varId);
729 newThresholds->add(t);
730 for(int j = 0; j < m_thresholds->size(); j++)
731 {
732 DCTableThreshold *old = m_thresholds->get(j);
733 if (old->getId() == t->getId())
734 {
735 t->copyState(old);
736 break;
737 }
738 }
739 }
740 delete m_thresholds;
741 m_thresholds = newThresholds;
742
743 unlock();
744 }
745
746 /**
747 * Get last collected value
748 */
749 void DCTable::fillLastValueMessage(NXCPMessage *msg)
750 {
751 lock();
752 if (m_lastValue != NULL)
753 {
754 m_lastValue->fillMessage(*msg, 0, -1);
755 }
756 unlock();
757 }
758
759 /**
760 * Get summary of last collected value (to show along simple DCI values)
761 */
762 void DCTable::fillLastValueSummaryMessage(NXCPMessage *pMsg, UINT32 dwId)
763 {
764 lock();
765 pMsg->setField(dwId++, m_id);
766 pMsg->setField(dwId++, m_name);
767 pMsg->setField(dwId++, m_description);
768 pMsg->setField(dwId++, (WORD)m_source);
769 pMsg->setField(dwId++, (WORD)DCI_DT_NULL); // compatibility: data type
770 pMsg->setField(dwId++, _T("")); // compatibility: value
771 pMsg->setField(dwId++, (UINT32)m_tLastPoll);
772 pMsg->setField(dwId++, (WORD)(matchClusterResource() ? m_status : ITEM_STATUS_DISABLED)); // show resource-bound DCIs as inactive if cluster resource is not on this node
773 pMsg->setField(dwId++, (WORD)getType());
774 pMsg->setField(dwId++, m_dwErrorCount);
775 pMsg->setField(dwId++, m_dwTemplateItemId);
776 pMsg->setField(dwId++, (WORD)0); // compatibility: number of thresholds
777
778 unlock();
779 }
780
781 /**
782 * Get data type of given column
783 */
784 int DCTable::getColumnDataType(const TCHAR *name)
785 {
786 int dt = DCI_DT_STRING;
787 bool found = false;
788
789 lock();
790
791 // look in column definition first
792 for(int i = 0; i < m_columns->size(); i++)
793 {
794 DCTableColumn *column = m_columns->get(i);
795 if (!_tcsicmp(column->getName(), name))
796 {
797 dt = column->getDataType();
798 break;
799 }
800 }
801
802 // use last values if not found in definitions
803 if (!found && (m_lastValue != NULL))
804 {
805 int index = m_lastValue->getColumnIndex(name);
806 if (index != -1)
807 dt = m_lastValue->getColumnDataType(index);
808 }
809
810 unlock();
811 return dt;
812 }
813
814 /**
815 * Get last collected value
816 */
817 Table *DCTable::getLastValue()
818 {
819 lock();
820 Table *value;
821 if (m_lastValue != NULL)
822 {
823 value = m_lastValue;
824 value->incRefCount();
825 }
826 else
827 {
828 value = NULL;
829 }
830 unlock();
831 return value;
832 }
833
834 /**
835 * Update destination value from source value
836 */
837 #define RECALCULATE_VALUE(dst, src, func, count) \
838 { \
839 switch(func) \
840 { \
841 case DCF_FUNCTION_MIN: \
842 if (src < dst) dst = src; \
843 break; \
844 case DCF_FUNCTION_MAX: \
845 if (src > dst) dst = src; \
846 break; \
847 case DCF_FUNCTION_SUM: \
848 dst += src; \
849 break; \
850 case DCF_FUNCTION_AVG: \
851 dst = (dst * count + src) / (count + 1); \
852 break; \
853 } \
854 }
855
856 /**
857 * Merge values
858 */
859 void DCTable::mergeValues(Table *dest, Table *src, int count)
860 {
861 for(int sRow = 0; sRow < src->getNumRows(); sRow++)
862 {
863 TCHAR instance[MAX_RESULT_LENGTH];
864
865 src->buildInstanceString(sRow, instance, MAX_RESULT_LENGTH);
866 int dRow = dest->findRowByInstance(instance);
867 if (dRow >= 0)
868 {
869 for(int j = 0; j < m_columns->size(); j++)
870 {
871 DCTableColumn *cd = m_columns->get(j);
872 if ((cd == NULL) || cd->isInstanceColumn() || (cd->getDataType() == DCI_DT_STRING))
873 continue;
874 int column = dest->getColumnIndex(cd->getName());
875 if (column == -1)
876 continue;
877
878 if (cd->getDataType() == DCI_DT_FLOAT)
879 {
880 double sval = src->getAsDouble(sRow, column);
881 double dval = dest->getAsDouble(dRow, column);
882
883 RECALCULATE_VALUE(dval, sval, cd->getAggregationFunction(), count);
884
885 dest->setAt(dRow, column, dval);
886 }
887 else if ((cd->getDataType() == DCI_DT_UINT) || (cd->getDataType() == DCI_DT_UINT64))
888 {
889 UINT64 sval = src->getAsUInt64(sRow, column);
890 UINT64 dval = dest->getAsUInt64(dRow, column);
891
892 RECALCULATE_VALUE(dval, sval, cd->getAggregationFunction(), count);
893
894 dest->setAt(dRow, column, dval);
895 }
896 else
897 {
898 INT64 sval = src->getAsInt64(sRow, column);
899 INT64 dval = dest->getAsInt64(dRow, column);
900
901 RECALCULATE_VALUE(dval, sval, cd->getAggregationFunction(), count);
902
903 dest->setAt(dRow, column, dval);
904 }
905 }
906 }
907 else
908 {
909 // no such instance
910 dest->copyRow(src, sRow);
911 }
912 }
913 }
914
915 /**
916 * Update columns in resulting table according to definition
917 */
918 void DCTable::updateResultColumns(Table *t)
919 {
920 lock();
921 for(int i = 0; i < m_columns->size(); i++)
922 {
923 DCTableColumn *col = m_columns->get(i);
924 int index = t->getColumnIndex(col->getName());
925 if (index != -1)
926 {
927 TableColumnDefinition *cd = t->getColumnDefinitions()->get(index);
928 if (cd != NULL)
929 {
930 cd->setDataType(col->getDataType());
931 cd->setInstanceColumn(col->isInstanceColumn());
932 cd->setDisplayName(col->getDisplayName());
933 }
934 }
935 }
936 unlock();
937 }
938
939 /**
940 * Update from template item
941 */
942 void DCTable::updateFromTemplate(DCObject *src)
943 {
944 DCObject::updateFromTemplate(src);
945
946 if (src->getType() != DCO_TYPE_TABLE)
947 {
948 DbgPrintf(2, _T("INTERNAL ERROR: DCTable::updateFromTemplate(%d, %d): source type is %d"), (int)m_id, (int)src->getId(), src->getType());
949 return;
950 }
951
952 lock();
953 DCTable *table = (DCTable *)src;
954
955 m_columns->clear();
956 for(int i = 0; i < table->m_columns->size(); i++)
957 m_columns->add(new DCTableColumn(table->m_columns->get(i)));
958
959 m_thresholds->clear();
960 for(int i = 0; i < table->m_thresholds->size(); i++)
961 m_thresholds->add(new DCTableThreshold(table->m_thresholds->get(i)));
962
963 unlock();
964 }
965
966 /**
967 * Create management pack record
968 */
969 void DCTable::createExportRecord(String &str)
970 {
971 lock();
972
973 str.appendFormattedString(_T("\t\t\t\t<dctable id=\"%d\">\n")
974 _T("\t\t\t\t\t<guid>%s</guid>\n")
975 _T("\t\t\t\t\t<name>%s</name>\n")
976 _T("\t\t\t\t\t<description>%s</description>\n")
977 _T("\t\t\t\t\t<origin>%d</origin>\n")
978 _T("\t\t\t\t\t<interval>%d</interval>\n")
979 _T("\t\t\t\t\t<retention>%d</retention>\n")
980 _T("\t\t\t\t\t<systemTag>%s</systemTag>\n")
981 _T("\t\t\t\t\t<flags>%d</flags>\n")
982 _T("\t\t\t\t\t<snmpPort>%d</snmpPort>\n"),
983 (int)m_id, (const TCHAR *)m_guid.toString(),
984 (const TCHAR *)EscapeStringForXML2(m_name),
985 (const TCHAR *)EscapeStringForXML2(m_description),
986 (int)m_source, m_iPollingInterval, m_iRetentionTime,
987 (const TCHAR *)EscapeStringForXML2(m_systemTag),
988 (int)m_flags, (int)m_snmpPort);
989
990 if (m_transformationScriptSource != NULL)
991 {
992 str.append(_T("\t\t\t\t\t<transformation>"));
993 str.appendPreallocated(EscapeStringForXML(m_transformationScriptSource, -1));
994 str.append(_T("</transformation>\n"));
995 }
996
997 if ((m_schedules != NULL) && (m_schedules->size() > 0))
998 {
999 str.append(_T("\t\t\t\t\t<schedules>\n"));
1000 for(int i = 0; i < m_schedules->size(); i++)
1001 {
1002 str.append(_T("\t\t\t\t\t\t<schedule>"));
1003 str.append(EscapeStringForXML2(m_schedules->get(i)));
1004 str.append(_T("</schedule>\n"));
1005 }
1006 str.append(_T("\t\t\t\t\t</schedules>\n"));
1007 }
1008
1009 if (m_columns != NULL)
1010 {
1011 str += _T("\t\t\t\t\t<columns>\n");
1012 for(int i = 0; i < m_columns->size(); i++)
1013 {
1014 m_columns->get(i)->createNXMPRecord(str, i + 1);
1015 }
1016 str += _T("\t\t\t\t\t</columns>\n");
1017 }
1018
1019 if (m_thresholds != NULL)
1020 {
1021 str += _T("\t\t\t\t\t<thresholds>\n");
1022 for(int i = 0; i < m_thresholds->size(); i++)
1023 {
1024 m_thresholds->get(i)->createNXMPRecord(str, i + 1);
1025 }
1026 str += _T("\t\t\t\t\t</thresholds>\n");
1027 }
1028
1029 if (m_pszPerfTabSettings != NULL)
1030 {
1031 str.append(_T("\t\t\t\t\t<perfTabSettings>"));
1032 str.appendPreallocated(EscapeStringForXML(m_pszPerfTabSettings, -1));
1033 str.append(_T("</perfTabSettings>\n"));
1034 }
1035
1036 unlock();
1037 str.append(_T("\t\t\t\t</dctable>\n"));
1038 }
1039
1040 /**
1041 * Create DCObject from import file
1042 */
1043 void DCTable::updateFromImport(ConfigEntry *config)
1044 {
1045 DCObject::updateFromImport(config);
1046
1047 lock();
1048 m_columns->clear();
1049 ConfigEntry *columnsRoot = config->findEntry(_T("columns"));
1050 if (columnsRoot != NULL)
1051 {
1052 ObjectArray<ConfigEntry> *columns = columnsRoot->getSubEntries(_T("column#*"));
1053 for(int i = 0; i < columns->size(); i++)
1054 {
1055 m_columns->add(new DCTableColumn(columns->get(i)));
1056 }
1057 delete columns;
1058 }
1059
1060 m_thresholds->clear();
1061 ConfigEntry *thresholdsRoot = config->findEntry(_T("thresholds"));
1062 if (thresholdsRoot != NULL)
1063 {
1064 ObjectArray<ConfigEntry> *thresholds = thresholdsRoot->getSubEntries(_T("threshold#*"));
1065 for(int i = 0; i < thresholds->size(); i++)
1066 {
1067 m_thresholds->add(new DCTableThreshold(thresholds->get(i)));
1068 }
1069 delete thresholds;
1070 }
1071 unlock();
1072 }
1073
1074 /**
1075 * Should return true if object has (or can have) value
1076 */
1077 bool DCTable::hasValue()
1078 {
1079 if (m_owner->getObjectClass() == OBJECT_CLUSTER)
1080 return isAggregateOnCluster();
1081 return true;
1082 }