object save optimization - object properties divided into groups and anly modified...
[public/netxms.git] / src / server / core / svccontainer.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 Raden Solutions
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: svccontainer.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 #define QUERY_LENGTH (512)
26
27 /**
28 * Service log record ID
29 */
30 INT32 ServiceContainer::logRecordId = -1;
31
32 /**
33 * Default constructor for service service object
34 */
35 ServiceContainer::ServiceContainer() : Container()
36 {
37 initServiceContainer();
38 }
39
40 /**
41 * Create new service container object
42 */
43 ServiceContainer::ServiceContainer(const TCHAR *pszName) : Container(pszName, 0)
44 {
45 initServiceContainer();
46 }
47
48 void ServiceContainer::initServiceContainer()
49 {
50 m_prevUptimeUpdateStatus = STATUS_UNKNOWN;
51 m_prevUptimeUpdateTime = time(NULL);
52 m_uptimeDay = 100.0;
53 m_uptimeWeek = 100.0;
54 m_uptimeMonth = 100.0;
55 m_downtimeDay = 0;
56 m_downtimeWeek = 0;
57 m_downtimeMonth = 0;
58 m_prevDiffDay = 0;
59 m_prevDiffWeek = 0;
60 m_prevDiffMonth = 0;
61 }
62
63 /**
64 * Create object from database data
65 */
66 bool ServiceContainer::loadFromDatabase(DB_HANDLE hdb, UINT32 id)
67 {
68 if (!Container::loadFromDatabase(hdb, id))
69 return false;
70
71 initUptimeStats();
72 return true;
73 }
74
75 /**
76 * Save object to database
77 */
78 bool ServiceContainer::saveToDatabase(DB_HANDLE hdb)
79 {
80 return Container::saveToDatabase(hdb);
81 }
82
83 /**
84 * Delete object from database
85 */
86 bool ServiceContainer::deleteFromDatabase(DB_HANDLE hdb)
87 {
88 return Container::deleteFromDatabase(hdb);
89 }
90
91 /**
92 * Create NXCP message with object's data
93 */
94 void ServiceContainer::fillMessageInternal(NXCPMessage *pMsg)
95 {
96 Container::fillMessageInternal(pMsg);
97 pMsg->setField(VID_UPTIME_DAY, m_uptimeDay);
98 pMsg->setField(VID_UPTIME_WEEK, m_uptimeWeek);
99 pMsg->setField(VID_UPTIME_MONTH, m_uptimeMonth);
100 }
101
102 /**
103 * Modify object from message
104 */
105 UINT32 ServiceContainer::modifyFromMessageInternal(NXCPMessage *pRequest)
106 {
107 return Container::modifyFromMessageInternal(pRequest);
108 }
109
110 /**
111 * Calculate status for compound object based on childs status
112 */
113 void ServiceContainer::calculateCompoundStatus(BOOL bForcedRecalc)
114 {
115 int i, iCount, iMostCriticalStatus;
116 int iOldStatus = m_status;
117
118 DbgPrintf(7, _T("ServiceContainer::calculateCompoundStatus() for %s [%d]"), m_name, m_id);
119
120 // Calculate own status by selecting the most critical status of the kids
121 lockChildList(false);
122 for(i = 0, iCount = 0, iMostCriticalStatus = -1; i < m_childList->size(); i++)
123 {
124 int iChildStatus = m_childList->get(i)->getStatus();
125 if ((iChildStatus < STATUS_UNKNOWN) &&
126 (iChildStatus > iMostCriticalStatus))
127 {
128 iMostCriticalStatus = iChildStatus;
129 iCount++;
130 }
131 }
132 // Set status and update uptime counters
133 setStatus((iCount > 0) ? iMostCriticalStatus : STATUS_UNKNOWN);
134 unlockChildList();
135
136 // Cause parent object(s) to recalculate it's status
137 if ((iOldStatus != m_status) || bForcedRecalc)
138 {
139 lockParentList(false);
140 for(i = 0; i < m_parentList->size(); i++)
141 m_parentList->get(i)->calculateCompoundStatus();
142 unlockParentList();
143 lockProperties();
144 setModified(MODIFY_COMMON_PROPERTIES);
145 unlockProperties();
146 }
147
148 DbgPrintf(6, _T("ServiceContainer::calculateCompoundStatus(%s [%d]): old_status=%d new_status=%d"), m_name, m_id, iOldStatus, m_status);
149
150 if (iOldStatus != STATUS_UNKNOWN && iOldStatus != m_status)
151 addHistoryRecord();
152 }
153
154 /**
155 * Set service status - use this instead of direct assignment;
156 */
157 void ServiceContainer::setStatus(int newStatus)
158 {
159 m_status = newStatus;
160 }
161
162 /**
163 * Add a record to slm_service_history table
164 */
165 BOOL ServiceContainer::addHistoryRecord()
166 {
167 DB_RESULT hResult;
168 DB_STATEMENT hStmt;
169
170 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
171
172 if (ServiceContainer::logRecordId < 0)
173 {
174 hResult = DBSelect(hdb, _T("SELECT max(record_id) FROM slm_service_history"));
175 if (hResult == NULL)
176 {
177 DBConnectionPoolReleaseConnection(hdb);
178 return FALSE;
179 }
180 ServiceContainer::logRecordId = DBGetNumRows(hResult) > 0 ? DBGetFieldLong(hResult, 0, 0) : 0;
181 DBFreeResult(hResult);
182 }
183
184 ServiceContainer::logRecordId++;
185
186 hStmt = DBPrepare(hdb, _T("INSERT INTO slm_service_history (record_id,service_id,change_timestamp,new_status) ")
187 _T("VALUES (?,?,?,?)"));
188 if (hStmt != NULL)
189 {
190 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, ServiceContainer::logRecordId);
191 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, m_id);
192 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, UINT32(time(NULL)));
193 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, UINT32(m_status));
194 if (!DBExecute(hStmt))
195 {
196 DBFreeStatement(hStmt);
197 DBConnectionPoolReleaseConnection(hdb);
198 return FALSE;
199 }
200 DbgPrintf(9, _T("ServiceContainer::addHistoryRecord() ok with id %d"), ServiceContainer::logRecordId);
201 }
202 else
203 {
204 DBConnectionPoolReleaseConnection(hdb);
205 return FALSE;
206 }
207
208 DBFreeStatement(hStmt);
209 DBConnectionPoolReleaseConnection(hdb);
210 return TRUE;
211 }
212
213 /**
214 * Initialize uptime statistics (daily, weekly, monthly) by examining slm_service_history
215 */
216 void ServiceContainer::initUptimeStats()
217 {
218 lockProperties();
219 m_prevUptimeUpdateStatus = m_status;
220 m_uptimeDay = getUptimeFromDBFor(DAY, &m_downtimeDay);
221 m_uptimeWeek = getUptimeFromDBFor(WEEK, &m_downtimeWeek);
222 m_uptimeMonth = getUptimeFromDBFor(MONTH, &m_downtimeMonth);
223 unlockProperties();
224 DbgPrintf(6, _T("ServiceContainer::initUptimeStats() %s [%d] %lf %lf %lf"), m_name, m_id, m_uptimeDay, m_uptimeWeek, m_uptimeMonth);
225 }
226
227 /**
228 * Calculate uptime for given period using data in database
229 */
230 double ServiceContainer::getUptimeFromDBFor(Period period, INT32 *downtime)
231 {
232 time_t beginTime;
233 INT32 timediffTillNow = ServiceContainer::getSecondsSinceBeginningOf(period, &beginTime);
234 double percentage = 0;
235
236 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
237 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT change_timestamp,new_status FROM slm_service_history ")
238 _T("WHERE service_id=? AND change_timestamp>?"));
239 if (hStmt != NULL)
240 {
241 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
242 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, (UINT32)beginTime);
243 DB_RESULT hResult = DBSelectPrepared(hStmt);
244 if (hResult == NULL)
245 {
246 DBFreeStatement(hStmt);
247 DBConnectionPoolReleaseConnection(hdb);
248 return percentage;
249 }
250
251 time_t changeTimestamp, prevChangeTimestamp = beginTime;
252 int newStatus = STATUS_UNKNOWN, i, realRows;
253 int numRows = DBGetNumRows(hResult);
254 *downtime = 0;
255 for (i = 0, realRows = 0; i < numRows; i++)
256 {
257 changeTimestamp = DBGetFieldLong(hResult, i, 0);
258 newStatus = DBGetFieldLong(hResult, i, 1);
259 if (newStatus == STATUS_UNKNOWN) // Malawi hotfix - ignore unknown status
260 continue;
261 if (newStatus == STATUS_NORMAL)
262 *downtime += (LONG)(changeTimestamp - prevChangeTimestamp);
263 else
264 prevChangeTimestamp = changeTimestamp;
265 realRows++;
266 }
267 if (newStatus == STATUS_CRITICAL) // the service is still down, add period till now
268 *downtime += LONG(time(NULL) - prevChangeTimestamp);
269 // no rows for period && critical status -> downtime from beginning till now
270 if (realRows == 0 && m_status == STATUS_CRITICAL)
271 *downtime = timediffTillNow;
272 percentage = 100.0 - (double)(*downtime * 100) / (double)getSecondsInPeriod(period);
273 DbgPrintf(7, _T("++++ ServiceContainer::getUptimeFromDBFor(), downtime %ld"), *downtime);
274
275 DBFreeResult(hResult);
276 DBFreeStatement(hStmt);
277 }
278
279 DBConnectionPoolReleaseConnection(hdb);
280 return percentage;
281 }
282
283 /**
284 * Update uptime counters
285 */
286 void ServiceContainer::updateUptimeStats(time_t currentTime, BOOL updateChilds)
287 {
288 LONG timediffTillNow;
289 LONG downtimeBetweenPolls = 0;
290
291 if (currentTime == 0)
292 currentTime = time(NULL);
293
294 lockProperties();
295
296 double prevUptimeDay = m_uptimeDay;
297 double prevUptimeWeek = m_uptimeWeek;
298 double prevUptimeMonth = m_uptimeMonth;
299
300 if (m_status == STATUS_CRITICAL && m_prevUptimeUpdateStatus == STATUS_CRITICAL)
301 {
302 downtimeBetweenPolls = LONG(currentTime - m_prevUptimeUpdateTime);
303 DbgPrintf(7, _T("++++ ServiceContainer::updateUptimeStats() both statuses critical"));
304 }
305
306 timediffTillNow = ServiceContainer::getSecondsSinceBeginningOf(DAY, NULL);
307 m_downtimeDay += downtimeBetweenPolls;
308 if (timediffTillNow < m_prevDiffDay)
309 m_downtimeDay = 0;
310 m_uptimeDay = 100.0 - (double)(m_downtimeDay * 100) / (double)ServiceContainer::getSecondsInPeriod(DAY);
311 m_prevDiffDay = timediffTillNow;
312 DbgPrintf(7, _T("++++ ServiceContainer::updateUptimeStats() m_downtimeDay %ld, timediffTillNow %ld, downtimeBetweenPolls %ld"),
313 m_downtimeDay, timediffTillNow, downtimeBetweenPolls);
314
315 timediffTillNow = ServiceContainer::getSecondsSinceBeginningOf(WEEK, NULL);
316 m_downtimeWeek += downtimeBetweenPolls;
317 if (timediffTillNow < m_prevDiffWeek)
318 m_downtimeWeek = 0;
319 m_uptimeWeek = 100.0 - (double)(m_downtimeWeek * 100) / (double)ServiceContainer::getSecondsInPeriod(WEEK);
320 m_prevDiffWeek = timediffTillNow;
321
322 timediffTillNow = ServiceContainer::getSecondsSinceBeginningOf(MONTH, NULL);
323 m_downtimeMonth += downtimeBetweenPolls;
324 if (timediffTillNow < m_prevDiffMonth)
325 m_downtimeMonth = 0;
326 m_uptimeMonth = 100.0 - (double)(m_downtimeMonth * 100) / (double)ServiceContainer::getSecondsInPeriod(MONTH);
327 m_prevDiffMonth = timediffTillNow;
328
329 if ((prevUptimeDay != m_uptimeDay) || (prevUptimeWeek != m_uptimeWeek) || (prevUptimeMonth != m_uptimeMonth))
330 {
331 setModified(MODIFY_OTHER);
332 }
333 unlockProperties();
334
335 m_prevUptimeUpdateStatus = m_status;
336 m_prevUptimeUpdateTime = currentTime;
337
338 DbgPrintf(7, _T("++++ ServiceContainer::updateUptimeStats() [%d] %lf %lf %lf"), int(m_id), m_uptimeDay, m_uptimeWeek, m_uptimeMonth);
339
340 if (updateChilds)
341 {
342 lockChildList(false);
343 for(int i = 0; i < m_childList->size(); i++)
344 {
345 NetObj *child = m_childList->get(i);
346 if (child->getObjectClass() == OBJECT_BUSINESSSERVICE || child->getObjectClass() == OBJECT_NODELINK)
347 ((ServiceContainer*)child)->updateUptimeStats(currentTime, TRUE);
348 }
349 unlockChildList();
350 }
351 }
352
353 /**
354 * Calculate number of seconds since the beginning of given period
355 */
356 INT32 ServiceContainer::getSecondsSinceBeginningOf(Period period, time_t *beginTime)
357 {
358 time_t curTime = time(NULL);
359 struct tm tmBuffer;
360
361 #if HAVE_LOCALTIME_R
362 localtime_r(&curTime, &tmBuffer);
363 #else
364 struct tm *tms = localtime(&curTime);
365 memcpy(&tmBuffer, tms, sizeof(struct tm));
366 #endif
367
368 tmBuffer.tm_hour = 0;
369 tmBuffer.tm_min = 0;
370 tmBuffer.tm_sec = 0;
371 if (period == MONTH)
372 tmBuffer.tm_mday = 1;
373 time_t beginTimeL = mktime(&tmBuffer);
374 if (period == WEEK)
375 {
376 if (tmBuffer.tm_wday == 0)
377 tmBuffer.tm_wday = 7;
378 tmBuffer.tm_wday--;
379 beginTimeL -= 3600 * 24 * tmBuffer.tm_wday;
380 }
381
382 if (beginTime != NULL)
383 *beginTime = beginTimeL;
384
385 return (INT32)(curTime - beginTimeL);
386 }
387
388 /**
389 * Calculate number of seconds in the current month
390 */
391 INT32 ServiceContainer::getSecondsInMonth()
392 {
393 time_t curTime = time(NULL);
394 struct tm *tms;
395
396 #if HAVE_LOCALTIME_R
397 struct tm tmBuffer;
398 tms = localtime_r(&curTime, &tmBuffer);
399 #else
400 tms = localtime(&curTime);
401 #endif
402
403 int& month = tms->tm_mon;
404 int year = tms->tm_year + 1900;
405 int days = 31;
406
407 if (month == 3 || month == 5 || month == 8 || month == 10)
408 days = 30;
409 else if (month == 1) /* February */
410 days = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) ? 29 : 28;
411
412 return (INT32)(days * 24 * 3600);
413 }
414
415 /**
416 * Called by client session handler to check if threshold summary should be shown for this object.
417 */
418 bool ServiceContainer::showThresholdSummary()
419 {
420 return false;
421 }