(no commit message)
[public/netxms.git] / src / agent / subagents / oracle / main.cpp
CommitLineData
4d1753cd
VK
1/*\r
2** NetXMS subagent for Oracle monitoring\r
3** Copyright (C) 2009-2012 Raden Solutions\r
4**/\r
5\r
6#include "oracle_subagent.h"\r
7\r
8CONDITION g_shutdownCondition;\r
9MUTEX g_paramAccessMutex;\r
10int g_dbCount;\r
11DB_DRIVER g_driverHandle = NULL;\r
12DatabaseInfo g_dbInfo[MAX_DATABASES];\r
13DatabaseData g_dbData[MAX_DATABASES];\r
14\r
15THREAD_RESULT THREAD_CALL queryThread(void *arg);\r
16\r
17DBParameterGroup g_paramGroup[] = {\r
18 {\r
19 700, _T("Oracle.Sessions."),\r
20 _T("select ") DB_NULLARG_MAGIC _T(" ValueName, count(*) Count from v$session"),\r
21 2, { NULL }, 0\r
22 },\r
23 {\r
24 700, _T("Oracle.Cursors."),\r
25 _T("select ") DB_NULLARG_MAGIC _T(" ValueName, sum(a.value) Count from v$sesstat a, v$statname b, v$session s where a.statistic# = b.statistic# and s.sid=a.sid and b.name = 'opened cursors current'"),\r
26 2, { NULL }, 0\r
27 },\r
28 {\r
29 700, _T("Oracle.DBInfo."), // E001_DbInstanceStat\r
30 _T("select ") DB_NULLARG_MAGIC _T(" ValueName, name Name, to_char(created) CreateDate, log_mode LogMode, open_mode OpenMode from v$database"),\r
31 5, { NULL }, 0\r
32 },\r
33 {\r
34 700, _T("Oracle.Instance."),\r
35 _T("select ") DB_NULLARG_MAGIC _T(" ValueName, version Version, status Status, archiver ArchiverStatus, shutdown_pending ShutdownPending from v$instance"),\r
36 5, { NULL }, 0\r
37 },\r
38 {\r
39 1000, _T("Oracle.TableSpaces."),\r
40 _T("select d.tablespace_name ValueName, d.status Status, d.contents Type, to_char(round(used_percent,2)) UsedPct from dba_tablespaces d, dba_tablespace_usage_metrics m where d.tablespace_name=m.tablespace_name"),\r
41 3, { NULL }, 0\r
42 },\r
43 {\r
44 700, _T("Oracle.Dual."), // E077_DualExssRowStat\r
45 _T("select ") DB_NULLARG_MAGIC _T(" ValueName, decode(count(*),1,0,1) ExcessRows from dual"),\r
46 1, { NULL }, 0\r
47 },\r
48 {\r
49 700, _T("Oracle.Performance."), // E086_PhysReadsRate (HP OV gives per minute), but this query gives current count; Oracle E088_LogicReadsRate \r
50 _T("select ") DB_NULLARG_MAGIC _T(" ValueName, (select s.value PhysReads from v$sysstat s, v$statname n where n.name='physical reads' and n.statistic#=s.statistic#) PhysReads, ")\r
51 _T("(select s.value LogicReads from v$sysstat s, v$statname n where n.name='session logical reads' and n.statistic#=s.statistic#) LogicReads ")\r
52 _T("from DUAL "),\r
53 2, { NULL }, 0\r
54 },\r
55 {\r
56 700, _T("Oracle.CriticalStats."), // E007_TblSpcStatusCnt, E014_DataFStatusCnt, E016_SegmntExtendCnt, E067_RBSegmntStatCnt, E061_AutoArchvStatus, "Grid: Datafiles Need Media Recovery"\r
57 _T("select ") DB_NULLARG_MAGIC _T(" ValueName, (select count(*) TSOFF from DBA_TABLESPACES where status <> 'ONLINE') TSOffCount, ")\r
58 _T("(select count(*) DFOFF from V$DATAFILE where status not in ('ONLINE','SYSTEM')) DFOffCount, ")\r
59 _T("(select count(*) from DBA_SEGMENTS where max_extents = extents) FullSegmentsCount, ")\r
60 _T("(select count(*) from DBA_ROLLBACK_SEGS where status <> 'ONLINE') RBSegsNotOnlineCount, ")\r
61 _T("decode(sign(decode((select upper(log_mode) from v$database),'ARCHIVELOG',1,0)-")\r
62 _T("decode((select upper(value) from v$parameter where upper(name)='LOG_ARCHIVE_START'),'TRUE',1,0)),1, 1, 0) AutoArchivingOff, ")\r
63 _T("(SELECT count(file#) from v$datafile_header where recover ='YES') DatafilesNeedMediaRecovery, ")\r
64 _T("(SELECT count(*) FROM dba_jobs where NVL(failures,0) <> 0) FailedJobs ")\r
65 _T("from DUAL"), \r
66 5, { NULL }, 0\r
67 },\r
68 0\r
69};\r
70\r
71//\r
72// Handler functions\r
73//\r
74\r
75LONG getParameters(const TCHAR *parameter, const TCHAR *argument, TCHAR *value)\r
76{\r
77 LONG ret = SYSINFO_RC_UNSUPPORTED;\r
78 TCHAR dbId[MAX_STR];\r
79 TCHAR entity[MAX_STR];\r
80\r
81 // Get id of the database requested\r
82 if (!AgentGetParameterArg(parameter, 1, dbId, MAX_STR))\r
83 return ret;\r
84 if (!AgentGetParameterArg(parameter, 2, entity, MAX_STR) || entity[0] == _T('\0'))\r
85 nx_strncpy(entity, DB_NULLARG_MAGIC, MAX_STR);\r
86\r
87 AgentWriteDebugLog(5, _T("%s: ***** got request for params: dbid='%s', param='%s'"), MYNAMESTR, dbId, parameter);\r
88\r
89 // Loop through databases and find an entry in g_dbInfo[] for this id\r
90 for (int i = 0; i <= g_dbCount; i++)\r
91 {\r
92 if (!_tcsnicmp(g_dbInfo[i].id, dbId, MAX_STR)) // found DB\r
93 {\r
6534de9b
AK
94 if (argument[0] == _T('R'))\r
95 {\r
96 ret_string(value, g_dbInfo[i].connected ? _T("YES") : _T("NO"));\r
97 ret = SYSINFO_RC_SUCCESS;\r
98 }\r
4d1753cd 99 // Loop through parameter groups and check whose prefix matches the parameter requested\r
6534de9b 100 for (int k = 0; argument[0] == _T('X') && g_paramGroup[k].prefix; k++)\r
4d1753cd
VK
101 {\r
102 if (!_tcsnicmp(g_paramGroup[k].prefix, parameter, _tcslen(g_paramGroup[k].prefix))) // found prefix\r
103 {\r
104 MutexLock(g_dbInfo[i].accessMutex);\r
105 // Loop through the values\r
106 AgentWriteDebugLog(7, _T("%s: valuecount %d"), MYNAMESTR, g_paramGroup[k].valueCount[i]);\r
107 for (int j = 0; j < g_paramGroup[k].valueCount[i]; j++)\r
108 {\r
109 StringMap* map = (g_paramGroup[k].values[i])[j].attrs;\r
110 TCHAR* name = (g_paramGroup[k].values[i])[j].name;\r
111 if (!_tcsnicmp(name, entity, MAX_STR)) // found value which matches the parameters argument\r
112 {\r
113 TCHAR key[MAX_STR];\r
114 nx_strncpy(key, parameter + _tcslen(g_paramGroup[k].prefix), MAX_STR);\r
115 TCHAR* place = _tcschr(key, _T('('));\r
116 if (place != NULL)\r
117 {\r
118 *place = _T('\0');\r
119 const TCHAR* dbval = map->get(key);\r
120 ret_string(value, dbval);\r
121 ret = SYSINFO_RC_SUCCESS;\r
122 }\r
123 break;\r
124 }\r
125 }\r
126 MutexUnlock(g_dbInfo[i].accessMutex);\r
127\r
128 break;\r
129 }\r
130 }\r
131 break;\r
132 }\r
133 }\r
134\r
135 return ret;\r
136}\r
137\r
138\r
139//\r
140// Subagent initialization\r
141//\r
142\r
143static BOOL SubAgentInit(Config *config)\r
144{\r
145 BOOL result = TRUE;\r
146 static DatabaseInfo info;\r
147 int i;\r
148 static NX_CFG_TEMPLATE configTemplate[] = \r
149 {\r
150 { _T("Id"), CT_STRING, 0, 0, MAX_STR, 0, info.id }, \r
151 { _T("Name"), CT_STRING, 0, 0, MAX_STR, 0, info.name }, \r
6534de9b 152 { _T("TnsName"), CT_STRING, 0, 0, MAX_STR, 0, info.name }, \r
4d1753cd
VK
153 { _T("UserName"), CT_STRING, 0, 0, MAX_USERNAME, 0, info.username }, \r
154 { _T("Password"), CT_STRING, 0, 0, MAX_PASSWORD, 0, info.password },\r
155 { _T(""), CT_END_OF_LIST, 0, 0, 0, 0, NULL }\r
156 };\r
157\r
158 // Init db driver\r
159#ifdef _WIN32\r
160 g_driverHandle = DBLoadDriver(_T("oracle.ddr"), NULL, TRUE, NULL, NULL);\r
161#else\r
162 g_driverHandle = DBLoadDriver(LIBDIR "/libnxddr_oracle.so", NULL, TRUE, NULL, NULL);\r
163#endif\r
164 if (g_driverHandle == NULL)\r
165 {\r
166 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: failed to load db driver"), MYNAMESTR);\r
167 result = FALSE;\r
168 }\r
169\r
170 if (result)\r
171 {\r
172 g_shutdownCondition = ConditionCreate(TRUE);\r
173 }\r
174\r
04f06779
VK
175 // Load configuration from "oracle" section to allow simple configuration\r
176 // of one database without XML includes\r
177 memset(&info, 0, sizeof(info));\r
178 g_dbCount = -1;\r
179 if (config->parseTemplate(_T("ORACLE"), configTemplate))\r
180 {\r
2f67972d 181 if (info.name[0] != 0)\r
04f06779
VK
182 {\r
183 if (info.id[0] == 0)\r
2f67972d 184 _tcscpy(info.id, info.name);\r
04f06779
VK
185 memcpy(&g_dbInfo[++g_dbCount], &info, sizeof(DatabaseInfo));\r
186 g_dbInfo[g_dbCount].accessMutex = MutexCreate();\r
187 }\r
188 }\r
189\r
4d1753cd
VK
190 // Load configuration\r
191 for (g_dbCount = -1, i = 1; result && i <= MAX_DATABASES; i++)\r
192 {\r
193 TCHAR section[MAX_STR];\r
194 memset((void*)&info, 0, sizeof(info));\r
195 _sntprintf(section, MAX_STR, _T("oracle/databases/database#%d"), i);\r
196 if ((result = config->parseTemplate(section, configTemplate)) != TRUE)\r
197 {\r
198 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: error parsing configuration template"), MYNAMESTR);\r
199 return FALSE;\r
200 }\r
201 if (info.name[0] != _T('\0'))\r
202 memcpy((void*)&g_dbInfo[++g_dbCount], (void*)&info, sizeof(info));\r
203 else\r
204 continue;\r
205 if (info.username[0] == '\0' || info.password[0] == '\0')\r
206 {\r
207 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: error getting username and/or password for "), MYNAMESTR);\r
208 result = FALSE;\r
209 }\r
210 if (result && (g_dbInfo[g_dbCount].accessMutex = MutexCreate()) == NULL)\r
211 {\r
212 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: failed to create mutex (%d)"), MYNAMESTR, i);\r
213 result = FALSE;\r
214 }\r
215 }\r
216\r
217 // Exit if no usable configuration found\r
218 if (result && g_dbCount < 0)\r
219 {\r
220 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: no databases to monitor"), MYNAMESTR);\r
221 result = FALSE;\r
222 }\r
223\r
224 // Run query thread for each database configured\r
225 for (i = 0; result && i <= g_dbCount; i++)\r
226 {\r
227 g_dbInfo[i].queryThreadHandle = ThreadCreateEx(queryThread, 0, CAST_TO_POINTER(i, void *));\r
228 }\r
229\r
230 return result;\r
231}\r
232\r
233\r
234//\r
235// Shutdown handler\r
236//\r
237\r
238static void SubAgentShutdown(void)\r
239{\r
240 AgentWriteLog(EVENTLOG_INFORMATION_TYPE, _T("%s: shutting down"), MYNAMESTR);\r
241 ConditionSet(g_shutdownCondition);\r
242 for (int i = 0; i <= g_dbCount; i++)\r
243 {\r
244 ThreadJoin(g_dbInfo[i].queryThreadHandle);\r
245 MutexDestroy(g_dbInfo[i].accessMutex);\r
246 }\r
247 ConditionDestroy(g_shutdownCondition);\r
248}\r
249\r
250//\r
251// Figure out Oracle DBMS version\r
252//\r
253\r
254static int getOracleVersion(DB_HANDLE handle) \r
255{\r
256 TCHAR versionString[32];\r
257\r
258 DB_RESULT result = DBSelect(handle,_T("select version from v$instance"));\r
259 if (result == NULL) \r
260 {\r
2f67972d 261 AgentWriteLog(EVENTLOG_WARNING_TYPE, _T("%s: query from v$instance failed"), MYNAMESTR);\r
4d1753cd
VK
262 return 700; // assume Oracle 7.0 by default\r
263 }\r
264\r
265 DBGetField(result, 0, 0, versionString, 32);\r
266 int major = 0, minor = 0;\r
267 _stscanf(versionString, _T("%d.%d"), &major, &minor);\r
268 DBFreeResult(result);\r
269\r
270 return major * 100 + minor * 10;\r
271}\r
272\r
273//\r
274// Thread for SQL queries\r
275//\r
276\r
277THREAD_RESULT THREAD_CALL queryThread(void* arg)\r
278{\r
279 int dbIndex = CAST_FROM_POINTER(arg, int);\r
280 DatabaseInfo& db = g_dbInfo[dbIndex];\r
281 const DWORD pollInterval = 60 * 1000L; // 1 minute\r
282 int waitTimeout;\r
283 QWORD startTimeMs;\r
284 TCHAR errorText[DBDRV_MAX_ERROR_TEXT];\r
285\r
286 while (true)\r
287 {\r
6534de9b 288 db.handle = DBConnect(g_driverHandle, db.name, NULL /* db.server */, db.username, db.password, NULL, errorText);\r
4d1753cd
VK
289 if (db.handle != NULL)\r
290 {\r
291 AgentWriteLog(EVENTLOG_INFORMATION_TYPE, _T("%s: connected to DB"), MYNAMESTR);\r
292 db.connected = true;\r
293 db.version = getOracleVersion(db.handle);\r
294 }\r
295\r
296 while (db.connected)\r
297 {\r
298 startTimeMs = GetCurrentTimeMs();\r
299\r
300 // Do queries\r
301 if (!(db.connected = getParametersFromDB(dbIndex)))\r
302 {\r
303 break;\r
304 }\r
305\r
306 waitTimeout = pollInterval - DWORD(GetCurrentTimeMs() - startTimeMs);\r
307 if (ConditionWait(g_shutdownCondition, waitTimeout < 0 ? 1 : waitTimeout))\r
308 goto finish;\r
309 }\r
310\r
311 // Try to reconnect every 30 secs\r
312 if (ConditionWait(g_shutdownCondition, DWORD(30 * 1000)))\r
313 break;\r
314 }\r
315\r
316finish:\r
317 if (db.connected && db.handle != NULL)\r
318 {\r
319 DBDisconnect(db.handle);\r
320 }\r
321\r
322 return THREAD_OK;\r
323}\r
324\r
325\r
326bool getParametersFromDB( int dbIndex )\r
327{\r
328 bool ret = true;\r
329 DatabaseInfo& info = g_dbInfo[dbIndex];\r
330\r
331 if (!info.connected)\r
332 {\r
333 return false;\r
334 }\r
335\r
336 MutexLock(info.accessMutex);\r
337\r
338 for (int i = 0; g_paramGroup[i].prefix; i++)\r
339 {\r
340 AgentWriteDebugLog(7, _T("%s: got entry for '%s'"), MYNAMESTR, g_paramGroup[i].prefix);\r
341\r
342 if (g_paramGroup[i].version > info.version) // this parameter group is not supported for this DB\r
343 continue; \r
344\r
345 // Release previously allocated array of values for this group\r
346 for (int j = 0; j < g_paramGroup[i].valueCount[dbIndex]; j++)\r
347 delete (g_paramGroup[i].values[dbIndex])[j].attrs;\r
348 safe_free((void*)g_paramGroup[i].values[dbIndex]);\r
349\r
350 DB_RESULT queryResult = DBSelect(info.handle, g_paramGroup[i].query);\r
351 if (queryResult == NULL)\r
352 {\r
353 ret = false;\r
354 break;\r
355 }\r
356\r
357 int rows = DBGetNumRows(queryResult);\r
358 g_paramGroup[i].values[dbIndex] = (DBParameter*)malloc(sizeof(DBParameter) * rows);\r
359 g_paramGroup[i].valueCount[dbIndex] = rows;\r
360 for (int j = 0; j < rows; j++)\r
361 {\r
362 TCHAR colname[MAX_STR];\r
363 DBGetField(queryResult, j, 0, (g_paramGroup[i].values[dbIndex])[j].name, MAX_STR);\r
364 (g_paramGroup[i].values[dbIndex])[j].attrs = new StringMap;\r
365 for (int k = 1; DBGetColumnName(queryResult, k, colname, MAX_STR); k++) \r
366 {\r
367 TCHAR colval[MAX_STR];\r
368 DBGetField(queryResult, j, k, colval, MAX_STR);\r
369 // AgentWriteDebugLog(9, _T("%s: getParamsFromDB: colname '%s' ::: colval '%s'"), MYNAMESTR, colname, colval);\r
370 (g_paramGroup[i].values[dbIndex])[j].attrs->set(colname, colval);\r
371 }\r
372 }\r
373\r
374 DBFreeResult(queryResult);\r
375 }\r
376\r
377 MutexUnlock(info.accessMutex);\r
378\r
379 return ret;\r
380}\r
381\r
382//\r
383// Subagent information\r
384//\r
385\r
386\r
387static NETXMS_SUBAGENT_PARAM m_parameters[] =\r
388{\r
389 { _T("Oracle.Sessions.Count(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/Sessions: Number of sessions opened") },\r
390 { _T("Oracle.Cursors.Count(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/Cursors: Current number of opened cursors systemwide") },\r
6534de9b 391 { _T("Oracle.DBInfo.IsReachable(*)"), getParameters, "R", DCI_DT_STRING, _T("Oracle/Info: Database is reachable") },\r
4d1753cd
VK
392 { _T("Oracle.DBInfo.Name(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database name") },\r
393 { _T("Oracle.DBInfo.CreateDate(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database creation date") },\r
394 { _T("Oracle.DBInfo.LogMode(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database log mode") },\r
395 { _T("Oracle.DBInfo.OpenMode(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database open mode") },\r
396 { _T("Oracle.TableSpaces.Status(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Tablespaces: Status") },\r
397 { _T("Oracle.TableSpaces.Type(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Tablespaces: Type") },\r
398 { _T("Oracle.TableSpaces.UsedPct(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/Tablespaces: Percentage used") },\r
399 { _T("Oracle.Instance.Version(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: DBMS Version") },\r
400 { _T("Oracle.Instance.Status(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: Status") },\r
401 { _T("Oracle.Instance.ArchiverStatus(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: Archiver status") },\r
402 { _T("Oracle.Instance.ShutdownPending(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: Is shutdown pending") },\r
403 { _T("Oracle.CriticalStats.TSOffCount(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/CriticalStats: Number of offline tablespaces") },\r
404 { _T("Oracle.CriticalStats.DFOffCount(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/CriticalStats: Number of offline datafiles") },\r
405 { _T("Oracle.CriticalStats.FullSegmentsCount(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of segments that cannot extend") },\r
406 { _T("Oracle.CriticalStats.RBSegsNotOnlineCount(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of rollback segments not online") },\r
407 { _T("Oracle.CriticalStats.AutoArchivingOff(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/CriticalStats: Archive logs enabled but auto archiving off ") },\r
408 { _T("Oracle.CriticalStats.DatafilesNeedMediaRecovery(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of datafiles that need media recovery") },\r
409 { _T("Oracle.CriticalStats.FailedJobs(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of failed jobs") },\r
410 { _T("Oracle.Dual.ExcessRows(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/Dual: Excessive rows") },\r
411 { _T("Oracle.Performance.PhysReads(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/Performance: Number of physical reads") },\r
412 { _T("Oracle.Performance.LogicReads(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/Performance: Number of logical reads") }\r
413};\r
414\r
415/*\r
416static NETXMS_SUBAGENT_ENUM m_enums[] =\r
417{\r
418};\r
419*/\r
420\r
421static NETXMS_SUBAGENT_INFO m_info =\r
422{\r
423 NETXMS_SUBAGENT_INFO_MAGIC,\r
424 _T("ORACLE"), NETXMS_VERSION_STRING,\r
425 SubAgentInit, SubAgentShutdown, NULL,\r
426 sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM), m_parameters,\r
427 0, NULL,\r
428 /*sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM),\r
429 m_parameters,\r
430 sizeof(m_enums) / sizeof(NETXMS_SUBAGENT_ENUM),\r
431 m_enums,*/\r
432 0, NULL\r
433};\r
434\r
435\r
436//\r
437// Entry point for NetXMS agent\r
438//\r
439\r
440DECLARE_SUBAGENT_ENTRY_POINT(ORACLE)\r
441{\r
442 *ppInfo = &m_info;\r
443 return TRUE;\r
444}\r
445\r
446\r
447//\r
448// DLL entry point\r
449//\r
450\r
451#ifdef _WIN32\r
452\r
453BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)\r
454{\r
455 if (dwReason == DLL_PROCESS_ATTACH)\r
456 DisableThreadLibraryCalls(hInstance);\r
457 return TRUE;\r
458}\r
459\r
460#endif\r