preparation for tabular DCI support: code refactoring, list of supported tables cache...
[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
94 // Loop through parameter groups and check whose prefix matches the parameter requested\r
95 for (int k = 0; g_paramGroup[k].prefix; k++)\r
96 {\r
97 if (!_tcsnicmp(g_paramGroup[k].prefix, parameter, _tcslen(g_paramGroup[k].prefix))) // found prefix\r
98 {\r
99 MutexLock(g_dbInfo[i].accessMutex);\r
100 // Loop through the values\r
101 AgentWriteDebugLog(7, _T("%s: valuecount %d"), MYNAMESTR, g_paramGroup[k].valueCount[i]);\r
102 for (int j = 0; j < g_paramGroup[k].valueCount[i]; j++)\r
103 {\r
104 StringMap* map = (g_paramGroup[k].values[i])[j].attrs;\r
105 TCHAR* name = (g_paramGroup[k].values[i])[j].name;\r
106 if (!_tcsnicmp(name, entity, MAX_STR)) // found value which matches the parameters argument\r
107 {\r
108 TCHAR key[MAX_STR];\r
109 nx_strncpy(key, parameter + _tcslen(g_paramGroup[k].prefix), MAX_STR);\r
110 TCHAR* place = _tcschr(key, _T('('));\r
111 if (place != NULL)\r
112 {\r
113 *place = _T('\0');\r
114 const TCHAR* dbval = map->get(key);\r
115 ret_string(value, dbval);\r
116 ret = SYSINFO_RC_SUCCESS;\r
117 }\r
118 break;\r
119 }\r
120 }\r
121 MutexUnlock(g_dbInfo[i].accessMutex);\r
122\r
123 break;\r
124 }\r
125 }\r
126 break;\r
127 }\r
128 }\r
129\r
130 return ret;\r
131}\r
132\r
133\r
134//\r
135// Subagent initialization\r
136//\r
137\r
138static BOOL SubAgentInit(Config *config)\r
139{\r
140 BOOL result = TRUE;\r
141 static DatabaseInfo info;\r
142 int i;\r
143 static NX_CFG_TEMPLATE configTemplate[] = \r
144 {\r
145 { _T("Id"), CT_STRING, 0, 0, MAX_STR, 0, info.id }, \r
146 { _T("Name"), CT_STRING, 0, 0, MAX_STR, 0, info.name }, \r
147 { _T("Server"), CT_STRING, 0, 0, MAX_STR, 0, info.server }, \r
148 { _T("UserName"), CT_STRING, 0, 0, MAX_USERNAME, 0, info.username }, \r
149 { _T("Password"), CT_STRING, 0, 0, MAX_PASSWORD, 0, info.password },\r
150 { _T(""), CT_END_OF_LIST, 0, 0, 0, 0, NULL }\r
151 };\r
152\r
153 // Init db driver\r
154#ifdef _WIN32\r
155 g_driverHandle = DBLoadDriver(_T("oracle.ddr"), NULL, TRUE, NULL, NULL);\r
156#else\r
157 g_driverHandle = DBLoadDriver(LIBDIR "/libnxddr_oracle.so", NULL, TRUE, NULL, NULL);\r
158#endif\r
159 if (g_driverHandle == NULL)\r
160 {\r
161 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: failed to load db driver"), MYNAMESTR);\r
162 result = FALSE;\r
163 }\r
164\r
165 if (result)\r
166 {\r
167 g_shutdownCondition = ConditionCreate(TRUE);\r
168 }\r
169\r
04f06779
VK
170 // Load configuration from "oracle" section to allow simple configuration\r
171 // of one database without XML includes\r
172 memset(&info, 0, sizeof(info));\r
173 g_dbCount = -1;\r
174 if (config->parseTemplate(_T("ORACLE"), configTemplate))\r
175 {\r
2f67972d 176 if (info.name[0] != 0)\r
04f06779
VK
177 {\r
178 if (info.id[0] == 0)\r
2f67972d 179 _tcscpy(info.id, info.name);\r
04f06779
VK
180 memcpy(&g_dbInfo[++g_dbCount], &info, sizeof(DatabaseInfo));\r
181 g_dbInfo[g_dbCount].accessMutex = MutexCreate();\r
182 }\r
183 }\r
184\r
4d1753cd
VK
185 // Load configuration\r
186 for (g_dbCount = -1, i = 1; result && i <= MAX_DATABASES; i++)\r
187 {\r
188 TCHAR section[MAX_STR];\r
189 memset((void*)&info, 0, sizeof(info));\r
190 _sntprintf(section, MAX_STR, _T("oracle/databases/database#%d"), i);\r
191 if ((result = config->parseTemplate(section, configTemplate)) != TRUE)\r
192 {\r
193 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: error parsing configuration template"), MYNAMESTR);\r
194 return FALSE;\r
195 }\r
196 if (info.name[0] != _T('\0'))\r
197 memcpy((void*)&g_dbInfo[++g_dbCount], (void*)&info, sizeof(info));\r
198 else\r
199 continue;\r
200 if (info.username[0] == '\0' || info.password[0] == '\0')\r
201 {\r
202 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: error getting username and/or password for "), MYNAMESTR);\r
203 result = FALSE;\r
204 }\r
205 if (result && (g_dbInfo[g_dbCount].accessMutex = MutexCreate()) == NULL)\r
206 {\r
207 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: failed to create mutex (%d)"), MYNAMESTR, i);\r
208 result = FALSE;\r
209 }\r
210 }\r
211\r
212 // Exit if no usable configuration found\r
213 if (result && g_dbCount < 0)\r
214 {\r
215 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: no databases to monitor"), MYNAMESTR);\r
216 result = FALSE;\r
217 }\r
218\r
219 // Run query thread for each database configured\r
220 for (i = 0; result && i <= g_dbCount; i++)\r
221 {\r
222 g_dbInfo[i].queryThreadHandle = ThreadCreateEx(queryThread, 0, CAST_TO_POINTER(i, void *));\r
223 }\r
224\r
225 return result;\r
226}\r
227\r
228\r
229//\r
230// Shutdown handler\r
231//\r
232\r
233static void SubAgentShutdown(void)\r
234{\r
235 AgentWriteLog(EVENTLOG_INFORMATION_TYPE, _T("%s: shutting down"), MYNAMESTR);\r
236 ConditionSet(g_shutdownCondition);\r
237 for (int i = 0; i <= g_dbCount; i++)\r
238 {\r
239 ThreadJoin(g_dbInfo[i].queryThreadHandle);\r
240 MutexDestroy(g_dbInfo[i].accessMutex);\r
241 }\r
242 ConditionDestroy(g_shutdownCondition);\r
243}\r
244\r
245//\r
246// Figure out Oracle DBMS version\r
247//\r
248\r
249static int getOracleVersion(DB_HANDLE handle) \r
250{\r
251 TCHAR versionString[32];\r
252\r
253 DB_RESULT result = DBSelect(handle,_T("select version from v$instance"));\r
254 if (result == NULL) \r
255 {\r
2f67972d 256 AgentWriteLog(EVENTLOG_WARNING_TYPE, _T("%s: query from v$instance failed"), MYNAMESTR);\r
4d1753cd
VK
257 return 700; // assume Oracle 7.0 by default\r
258 }\r
259\r
260 DBGetField(result, 0, 0, versionString, 32);\r
261 int major = 0, minor = 0;\r
262 _stscanf(versionString, _T("%d.%d"), &major, &minor);\r
263 DBFreeResult(result);\r
264\r
265 return major * 100 + minor * 10;\r
266}\r
267\r
268//\r
269// Thread for SQL queries\r
270//\r
271\r
272THREAD_RESULT THREAD_CALL queryThread(void* arg)\r
273{\r
274 int dbIndex = CAST_FROM_POINTER(arg, int);\r
275 DatabaseInfo& db = g_dbInfo[dbIndex];\r
276 const DWORD pollInterval = 60 * 1000L; // 1 minute\r
277 int waitTimeout;\r
278 QWORD startTimeMs;\r
279 TCHAR errorText[DBDRV_MAX_ERROR_TEXT];\r
280\r
281 while (true)\r
282 {\r
283 db.handle = DBConnect(g_driverHandle, db.name, db.server, db.username, db.password, NULL, errorText);\r
284 if (db.handle != NULL)\r
285 {\r
286 AgentWriteLog(EVENTLOG_INFORMATION_TYPE, _T("%s: connected to DB"), MYNAMESTR);\r
287 db.connected = true;\r
288 db.version = getOracleVersion(db.handle);\r
289 }\r
290\r
291 while (db.connected)\r
292 {\r
293 startTimeMs = GetCurrentTimeMs();\r
294\r
295 // Do queries\r
296 if (!(db.connected = getParametersFromDB(dbIndex)))\r
297 {\r
298 break;\r
299 }\r
300\r
301 waitTimeout = pollInterval - DWORD(GetCurrentTimeMs() - startTimeMs);\r
302 if (ConditionWait(g_shutdownCondition, waitTimeout < 0 ? 1 : waitTimeout))\r
303 goto finish;\r
304 }\r
305\r
306 // Try to reconnect every 30 secs\r
307 if (ConditionWait(g_shutdownCondition, DWORD(30 * 1000)))\r
308 break;\r
309 }\r
310\r
311finish:\r
312 if (db.connected && db.handle != NULL)\r
313 {\r
314 DBDisconnect(db.handle);\r
315 }\r
316\r
317 return THREAD_OK;\r
318}\r
319\r
320\r
321bool getParametersFromDB( int dbIndex )\r
322{\r
323 bool ret = true;\r
324 DatabaseInfo& info = g_dbInfo[dbIndex];\r
325\r
326 if (!info.connected)\r
327 {\r
328 return false;\r
329 }\r
330\r
331 MutexLock(info.accessMutex);\r
332\r
333 for (int i = 0; g_paramGroup[i].prefix; i++)\r
334 {\r
335 AgentWriteDebugLog(7, _T("%s: got entry for '%s'"), MYNAMESTR, g_paramGroup[i].prefix);\r
336\r
337 if (g_paramGroup[i].version > info.version) // this parameter group is not supported for this DB\r
338 continue; \r
339\r
340 // Release previously allocated array of values for this group\r
341 for (int j = 0; j < g_paramGroup[i].valueCount[dbIndex]; j++)\r
342 delete (g_paramGroup[i].values[dbIndex])[j].attrs;\r
343 safe_free((void*)g_paramGroup[i].values[dbIndex]);\r
344\r
345 DB_RESULT queryResult = DBSelect(info.handle, g_paramGroup[i].query);\r
346 if (queryResult == NULL)\r
347 {\r
348 ret = false;\r
349 break;\r
350 }\r
351\r
352 int rows = DBGetNumRows(queryResult);\r
353 g_paramGroup[i].values[dbIndex] = (DBParameter*)malloc(sizeof(DBParameter) * rows);\r
354 g_paramGroup[i].valueCount[dbIndex] = rows;\r
355 for (int j = 0; j < rows; j++)\r
356 {\r
357 TCHAR colname[MAX_STR];\r
358 DBGetField(queryResult, j, 0, (g_paramGroup[i].values[dbIndex])[j].name, MAX_STR);\r
359 (g_paramGroup[i].values[dbIndex])[j].attrs = new StringMap;\r
360 for (int k = 1; DBGetColumnName(queryResult, k, colname, MAX_STR); k++) \r
361 {\r
362 TCHAR colval[MAX_STR];\r
363 DBGetField(queryResult, j, k, colval, MAX_STR);\r
364 // AgentWriteDebugLog(9, _T("%s: getParamsFromDB: colname '%s' ::: colval '%s'"), MYNAMESTR, colname, colval);\r
365 (g_paramGroup[i].values[dbIndex])[j].attrs->set(colname, colval);\r
366 }\r
367 }\r
368\r
369 DBFreeResult(queryResult);\r
370 }\r
371\r
372 MutexUnlock(info.accessMutex);\r
373\r
374 return ret;\r
375}\r
376\r
377//\r
378// Subagent information\r
379//\r
380\r
381\r
382static NETXMS_SUBAGENT_PARAM m_parameters[] =\r
383{\r
384 { _T("Oracle.Sessions.Count(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/Sessions: Number of sessions opened") },\r
385 { _T("Oracle.Cursors.Count(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/Cursors: Current number of opened cursors systemwide") },\r
386 { _T("Oracle.DBInfo.Name(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database name") },\r
387 { _T("Oracle.DBInfo.CreateDate(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database creation date") },\r
388 { _T("Oracle.DBInfo.LogMode(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database log mode") },\r
389 { _T("Oracle.DBInfo.OpenMode(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database open mode") },\r
390 { _T("Oracle.TableSpaces.Status(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Tablespaces: Status") },\r
391 { _T("Oracle.TableSpaces.Type(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Tablespaces: Type") },\r
392 { _T("Oracle.TableSpaces.UsedPct(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/Tablespaces: Percentage used") },\r
393 { _T("Oracle.Instance.Version(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: DBMS Version") },\r
394 { _T("Oracle.Instance.Status(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: Status") },\r
395 { _T("Oracle.Instance.ArchiverStatus(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: Archiver status") },\r
396 { _T("Oracle.Instance.ShutdownPending(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: Is shutdown pending") },\r
397 { _T("Oracle.CriticalStats.TSOffCount(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/CriticalStats: Number of offline tablespaces") },\r
398 { _T("Oracle.CriticalStats.DFOffCount(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/CriticalStats: Number of offline datafiles") },\r
399 { _T("Oracle.CriticalStats.FullSegmentsCount(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of segments that cannot extend") },\r
400 { _T("Oracle.CriticalStats.RBSegsNotOnlineCount(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of rollback segments not online") },\r
401 { _T("Oracle.CriticalStats.AutoArchivingOff(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/CriticalStats: Archive logs enabled but auto archiving off ") },\r
402 { _T("Oracle.CriticalStats.DatafilesNeedMediaRecovery(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of datafiles that need media recovery") },\r
403 { _T("Oracle.CriticalStats.FailedJobs(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of failed jobs") },\r
404 { _T("Oracle.Dual.ExcessRows(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/Dual: Excessive rows") },\r
405 { _T("Oracle.Performance.PhysReads(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/Performance: Number of physical reads") },\r
406 { _T("Oracle.Performance.LogicReads(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/Performance: Number of logical reads") }\r
407};\r
408\r
409/*\r
410static NETXMS_SUBAGENT_ENUM m_enums[] =\r
411{\r
412};\r
413*/\r
414\r
415static NETXMS_SUBAGENT_INFO m_info =\r
416{\r
417 NETXMS_SUBAGENT_INFO_MAGIC,\r
418 _T("ORACLE"), NETXMS_VERSION_STRING,\r
419 SubAgentInit, SubAgentShutdown, NULL,\r
420 sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM), m_parameters,\r
421 0, NULL,\r
422 /*sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM),\r
423 m_parameters,\r
424 sizeof(m_enums) / sizeof(NETXMS_SUBAGENT_ENUM),\r
425 m_enums,*/\r
426 0, NULL\r
427};\r
428\r
429\r
430//\r
431// Entry point for NetXMS agent\r
432//\r
433\r
434DECLARE_SUBAGENT_ENTRY_POINT(ORACLE)\r
435{\r
436 *ppInfo = &m_info;\r
437 return TRUE;\r
438}\r
439\r
440\r
441//\r
442// DLL entry point\r
443//\r
444\r
445#ifdef _WIN32\r
446\r
447BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)\r
448{\r
449 if (dwReason == DLL_PROCESS_ATTACH)\r
450 DisableThreadLibraryCalls(hInstance);\r
451 return TRUE;\r
452}\r
453\r
454#endif\r