9e41d6c9934de91321ac07f376d306304bb815c1
[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 setModified(); /* LOCK? */
144 }
145
146 DbgPrintf(6, _T("ServiceContainer::calculateCompoundStatus(%s [%d]): old_status=%d new_status=%d"), m_name, m_id, iOldStatus, m_status);
147
148 if (iOldStatus != STATUS_UNKNOWN && iOldStatus != m_status)
149 addHistoryRecord();
150 }
151
152 /**
153 * Set service status - use this instead of direct assignment;
154 */
155 void ServiceContainer::setStatus(int newStatus)
156 {
157 m_status = newStatus;
158 }
159
160 /**
161 * Add a record to slm_service_history table
162 */
163 BOOL ServiceContainer::addHistoryRecord()
164 {
165 DB_RESULT hResult;
166 DB_STATEMENT hStmt;
167
168 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
169
170 if (ServiceContainer::logRecordId < 0)
171 {
172 hResult = DBSelect(hdb, _T("SELECT max(record_id) FROM slm_service_history"));
173 if (hResult == NULL)
174 {
175 DBConnectionPoolReleaseConnection(hdb);
176 return FALSE;
177 }
178 ServiceContainer::logRecordId = DBGetNumRows(hResult) > 0 ? DBGetFieldLong(hResult, 0, 0) : 0;
179 DBFreeResult(hResult);
180 }
181
182 ServiceContainer::logRecordId++;
183
184 hStmt = DBPrepare(hdb, _T("INSERT INTO slm_service_history (record_id,service_id,change_timestamp,new_status) ")
185 _T("VALUES (?,?,?,?)"));
186 if (hStmt != NULL)
187 {
188 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, ServiceContainer::logRecordId);
189 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, m_id);
190 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, UINT32(time(NULL)));
191 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, UINT32(m_status));
192 if (!DBExecute(hStmt))
193 {
194 DBFreeStatement(hStmt);
195 DBConnectionPoolReleaseConnection(hdb);
196 return FALSE;
197 }
198 DbgPrintf(9, _T("ServiceContainer::addHistoryRecord() ok with id %d"), ServiceContainer::logRecordId);
199 }
200 else
201 {
202 DBConnectionPoolReleaseConnection(hdb);
203 return FALSE;
204 }
205
206 DBFreeStatement(hStmt);
207 DBConnectionPoolReleaseConnection(hdb);
208 return TRUE;
209 }
210
211 /**
212 * Initialize uptime statistics (daily, weekly, monthly) by examining slm_service_history
213 */
214 void ServiceContainer::initUptimeStats()
215 {
216 lockProperties();
217 m_prevUptimeUpdateStatus = m_status;
218 m_uptimeDay = getUptimeFromDBFor(DAY, &m_downtimeDay);
219 m_uptimeWeek = getUptimeFromDBFor(WEEK, &m_downtimeWeek);
220 m_uptimeMonth = getUptimeFromDBFor(MONTH, &m_downtimeMonth);
221 unlockProperties();
222 DbgPrintf(6, _T("ServiceContainer::initUptimeStats() %s [%d] %lf %lf %lf"), m_name, m_id, m_uptimeDay, m_uptimeWeek, m_uptimeMonth);
223 }
224
225 /**
226 * Calculate uptime for given period using data in database
227 */
228 double ServiceContainer::getUptimeFromDBFor(Period period, INT32 *downtime)
229 {
230 time_t beginTime;
231 INT32 timediffTillNow = ServiceContainer::getSecondsSinceBeginningOf(period, &beginTime);
232 double percentage = 0;
233
234 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
235 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT change_timestamp,new_status FROM slm_service_history ")
236 _T("WHERE service_id=? AND change_timestamp>?"));
237 if (hStmt != NULL)
238 {
239 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
240 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, (UINT32)beginTime);
241 DB_RESULT hResult = DBSelectPrepared(hStmt);
242 if (hResult == NULL)
243 {
244 DBFreeStatement(hStmt);
245 DBConnectionPoolReleaseConnection(hdb);
246 return percentage;
247 }
248
249 time_t changeTimestamp, prevChangeTimestamp = beginTime;
250 int newStatus = STATUS_UNKNOWN, i, realRows;
251 int numRows = DBGetNumRows(hResult);
252 *downtime = 0;
253 for (i = 0, realRows = 0; i < numRows; i++)
254 {
255 changeTimestamp = DBGetFieldLong(hResult, i, 0);
256 newStatus = DBGetFieldLong(hResult, i, 1);
257 if (newStatus == STATUS_UNKNOWN) // Malawi hotfix - ignore unknown status
258 continue;
259 if (newStatus == STATUS_NORMAL)
260 *downtime += (LONG)(changeTimestamp - prevChangeTimestamp);
261 else
262 prevChangeTimestamp = changeTimestamp;
263 realRows++;
264 }
265 if (newStatus == STATUS_CRITICAL) // the service is still down, add period till now
266 *downtime += LONG(time(NULL) - prevChangeTimestamp);
267 // no rows for period && critical status -> downtime from beginning till now
268 if (realRows == 0 && m_status == STATUS_CRITICAL)
269 *downtime = timediffTillNow;
270 percentage = 100.0 - (double)(*downtime * 100) / (double)getSecondsInPeriod(period);
271 DbgPrintf(7, _T("++++ ServiceContainer::getUptimeFromDBFor(), downtime %ld"), *downtime);
272
273 DBFreeResult(hResult);
274 DBFreeStatement(hStmt);
275 }
276
277 DBConnectionPoolReleaseConnection(hdb);
278 return percentage;
279 }
280
281 /**
282 * Update uptime counters
283 */
284 void ServiceContainer::updateUptimeStats(time_t currentTime, BOOL updateChilds)
285 {
286 LONG timediffTillNow;
287 LONG downtimeBetweenPolls = 0;
288
289 if (currentTime == 0)
290 currentTime = time(NULL);
291
292 lockProperties();
293
294 double prevUptimeDay = m_uptimeDay;
295 double prevUptimeWeek = m_uptimeWeek;
296 double prevUptimeMonth = m_uptimeMonth;
297
298 if (m_status == STATUS_CRITICAL && m_prevUptimeUpdateStatus == STATUS_CRITICAL)
299 {
300 downtimeBetweenPolls = LONG(currentTime - m_prevUptimeUpdateTime);
301 DbgPrintf(7, _T("++++ ServiceContainer::updateUptimeStats() both statuses critical"));
302 }
303
304 timediffTillNow = ServiceContainer::getSecondsSinceBeginningOf(DAY, NULL);
305 m_downtimeDay += downtimeBetweenPolls;
306 if (timediffTillNow < m_prevDiffDay)
307 m_downtimeDay = 0;
308 m_uptimeDay = 100.0 - (double)(m_downtimeDay * 100) / (double)ServiceContainer::getSecondsInPeriod(DAY);
309 m_prevDiffDay = timediffTillNow;
310 DbgPrintf(7, _T("++++ ServiceContainer::updateUptimeStats() m_downtimeDay %ld, timediffTillNow %ld, downtimeBetweenPolls %ld"),
311 m_downtimeDay, timediffTillNow, downtimeBetweenPolls);
312
313 timediffTillNow = ServiceContainer::getSecondsSinceBeginningOf(WEEK, NULL);
314 m_downtimeWeek += downtimeBetweenPolls;
315 if (timediffTillNow < m_prevDiffWeek)
316 m_downtimeWeek = 0;
317 m_uptimeWeek = 100.0 - (double)(m_downtimeWeek * 100) / (double)ServiceContainer::getSecondsInPeriod(WEEK);
318 m_prevDiffWeek = timediffTillNow;
319
320 timediffTillNow = ServiceContainer::getSecondsSinceBeginningOf(MONTH, NULL);
321 m_downtimeMonth += downtimeBetweenPolls;
322 if (timediffTillNow < m_prevDiffMonth)
323 m_downtimeMonth = 0;
324 m_uptimeMonth = 100.0 - (double)(m_downtimeMonth * 100) / (double)ServiceContainer::getSecondsInPeriod(MONTH);
325 m_prevDiffMonth = timediffTillNow;
326
327 if ((prevUptimeDay != m_uptimeDay) || (prevUptimeWeek != m_uptimeWeek) || (prevUptimeMonth != m_uptimeMonth))
328 {
329 setModified();
330 }
331 unlockProperties();
332
333 m_prevUptimeUpdateStatus = m_status;
334 m_prevUptimeUpdateTime = currentTime;
335
336 DbgPrintf(7, _T("++++ ServiceContainer::updateUptimeStats() [%d] %lf %lf %lf"), int(m_id), m_uptimeDay, m_uptimeWeek, m_uptimeMonth);
337
338 if (updateChilds)
339 {
340 lockChildList(false);
341 for(int i = 0; i < m_childList->size(); i++)
342 {
343 NetObj *child = m_childList->get(i);
344 if (child->getObjectClass() == OBJECT_BUSINESSSERVICE || child->getObjectClass() == OBJECT_NODELINK)
345 ((ServiceContainer*)child)->updateUptimeStats(currentTime, TRUE);
346 }
347 unlockChildList();
348 }
349 }
350
351 /**
352 * Calculate number of seconds since the beginning of given period
353 */
354 INT32 ServiceContainer::getSecondsSinceBeginningOf(Period period, time_t *beginTime)
355 {
356 time_t curTime = time(NULL);
357 struct tm tmBuffer;
358
359 #if HAVE_LOCALTIME_R
360 localtime_r(&curTime, &tmBuffer);
361 #else
362 struct tm *tms = localtime(&curTime);
363 memcpy(&tmBuffer, tms, sizeof(struct tm));
364 #endif
365
366 tmBuffer.tm_hour = 0;
367 tmBuffer.tm_min = 0;
368 tmBuffer.tm_sec = 0;
369 if (period == MONTH)
370 tmBuffer.tm_mday = 1;
371 time_t beginTimeL = mktime(&tmBuffer);
372 if (period == WEEK)
373 {
374 if (tmBuffer.tm_wday == 0)
375 tmBuffer.tm_wday = 7;
376 tmBuffer.tm_wday--;
377 beginTimeL -= 3600 * 24 * tmBuffer.tm_wday;
378 }
379
380 if (beginTime != NULL)
381 *beginTime = beginTimeL;
382
383 return (INT32)(curTime - beginTimeL);
384 }
385
386 /**
387 * Calculate number of seconds in the current month
388 */
389 INT32 ServiceContainer::getSecondsInMonth()
390 {
391 time_t curTime = time(NULL);
392 struct tm *tms;
393
394 #if HAVE_LOCALTIME_R
395 struct tm tmBuffer;
396 tms = localtime_r(&curTime, &tmBuffer);
397 #else
398 tms = localtime(&curTime);
399 #endif
400
401 int& month = tms->tm_mon;
402 int year = tms->tm_year + 1900;
403 int days = 31;
404
405 if (month == 3 || month == 5 || month == 8 || month == 10)
406 days = 30;
407 else if (month == 1) /* February */
408 days = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) ? 29 : 28;
409
410 return (INT32)(days * 24 * 3600);
411 }
412
413 /**
414 * Called by client session handler to check if threshold summary should be shown for this object.
415 */
416 bool ServiceContainer::showThresholdSummary()
417 {
418 return false;
419 }