oracle subagent moved to public tree
[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
170 // Load configuration\r
171 for (g_dbCount = -1, i = 1; result && i <= MAX_DATABASES; i++)\r
172 {\r
173 TCHAR section[MAX_STR];\r
174 memset((void*)&info, 0, sizeof(info));\r
175 _sntprintf(section, MAX_STR, _T("oracle/databases/database#%d"), i);\r
176 if ((result = config->parseTemplate(section, configTemplate)) != TRUE)\r
177 {\r
178 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: error parsing configuration template"), MYNAMESTR);\r
179 return FALSE;\r
180 }\r
181 if (info.name[0] != _T('\0'))\r
182 memcpy((void*)&g_dbInfo[++g_dbCount], (void*)&info, sizeof(info));\r
183 else\r
184 continue;\r
185 if (info.username[0] == '\0' || info.password[0] == '\0')\r
186 {\r
187 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: error getting username and/or password for "), MYNAMESTR);\r
188 result = FALSE;\r
189 }\r
190 if (result && (g_dbInfo[g_dbCount].accessMutex = MutexCreate()) == NULL)\r
191 {\r
192 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: failed to create mutex (%d)"), MYNAMESTR, i);\r
193 result = FALSE;\r
194 }\r
195 }\r
196\r
197 // Exit if no usable configuration found\r
198 if (result && g_dbCount < 0)\r
199 {\r
200 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("%s: no databases to monitor"), MYNAMESTR);\r
201 result = FALSE;\r
202 }\r
203\r
204 // Run query thread for each database configured\r
205 for (i = 0; result && i <= g_dbCount; i++)\r
206 {\r
207 g_dbInfo[i].queryThreadHandle = ThreadCreateEx(queryThread, 0, CAST_TO_POINTER(i, void *));\r
208 }\r
209\r
210 return result;\r
211}\r
212\r
213\r
214//\r
215// Shutdown handler\r
216//\r
217\r
218static void SubAgentShutdown(void)\r
219{\r
220 AgentWriteLog(EVENTLOG_INFORMATION_TYPE, _T("%s: shutting down"), MYNAMESTR);\r
221 ConditionSet(g_shutdownCondition);\r
222 for (int i = 0; i <= g_dbCount; i++)\r
223 {\r
224 ThreadJoin(g_dbInfo[i].queryThreadHandle);\r
225 MutexDestroy(g_dbInfo[i].accessMutex);\r
226 }\r
227 ConditionDestroy(g_shutdownCondition);\r
228}\r
229\r
230//\r
231// Figure out Oracle DBMS version\r
232//\r
233\r
234static int getOracleVersion(DB_HANDLE handle) \r
235{\r
236 TCHAR versionString[32];\r
237\r
238 DB_RESULT result = DBSelect(handle,_T("select version from v$instance"));\r
239 if (result == NULL) \r
240 {\r
241 AgentWriteLog(EVENTLOG_WARNING_TYPE, _T("%s: query from v$instance failed"));\r
242 return 700; // assume Oracle 7.0 by default\r
243 }\r
244\r
245 DBGetField(result, 0, 0, versionString, 32);\r
246 int major = 0, minor = 0;\r
247 _stscanf(versionString, _T("%d.%d"), &major, &minor);\r
248 DBFreeResult(result);\r
249\r
250 return major * 100 + minor * 10;\r
251}\r
252\r
253//\r
254// Thread for SQL queries\r
255//\r
256\r
257THREAD_RESULT THREAD_CALL queryThread(void* arg)\r
258{\r
259 int dbIndex = CAST_FROM_POINTER(arg, int);\r
260 DatabaseInfo& db = g_dbInfo[dbIndex];\r
261 const DWORD pollInterval = 60 * 1000L; // 1 minute\r
262 int waitTimeout;\r
263 QWORD startTimeMs;\r
264 TCHAR errorText[DBDRV_MAX_ERROR_TEXT];\r
265\r
266 while (true)\r
267 {\r
268 db.handle = DBConnect(g_driverHandle, db.name, db.server, db.username, db.password, NULL, errorText);\r
269 if (db.handle != NULL)\r
270 {\r
271 AgentWriteLog(EVENTLOG_INFORMATION_TYPE, _T("%s: connected to DB"), MYNAMESTR);\r
272 db.connected = true;\r
273 db.version = getOracleVersion(db.handle);\r
274 }\r
275\r
276 while (db.connected)\r
277 {\r
278 startTimeMs = GetCurrentTimeMs();\r
279\r
280 // Do queries\r
281 if (!(db.connected = getParametersFromDB(dbIndex)))\r
282 {\r
283 break;\r
284 }\r
285\r
286 waitTimeout = pollInterval - DWORD(GetCurrentTimeMs() - startTimeMs);\r
287 if (ConditionWait(g_shutdownCondition, waitTimeout < 0 ? 1 : waitTimeout))\r
288 goto finish;\r
289 }\r
290\r
291 // Try to reconnect every 30 secs\r
292 if (ConditionWait(g_shutdownCondition, DWORD(30 * 1000)))\r
293 break;\r
294 }\r
295\r
296finish:\r
297 if (db.connected && db.handle != NULL)\r
298 {\r
299 DBDisconnect(db.handle);\r
300 }\r
301\r
302 return THREAD_OK;\r
303}\r
304\r
305\r
306bool getParametersFromDB( int dbIndex )\r
307{\r
308 bool ret = true;\r
309 DatabaseInfo& info = g_dbInfo[dbIndex];\r
310\r
311 if (!info.connected)\r
312 {\r
313 return false;\r
314 }\r
315\r
316 MutexLock(info.accessMutex);\r
317\r
318 for (int i = 0; g_paramGroup[i].prefix; i++)\r
319 {\r
320 AgentWriteDebugLog(7, _T("%s: got entry for '%s'"), MYNAMESTR, g_paramGroup[i].prefix);\r
321\r
322 if (g_paramGroup[i].version > info.version) // this parameter group is not supported for this DB\r
323 continue; \r
324\r
325 // Release previously allocated array of values for this group\r
326 for (int j = 0; j < g_paramGroup[i].valueCount[dbIndex]; j++)\r
327 delete (g_paramGroup[i].values[dbIndex])[j].attrs;\r
328 safe_free((void*)g_paramGroup[i].values[dbIndex]);\r
329\r
330 DB_RESULT queryResult = DBSelect(info.handle, g_paramGroup[i].query);\r
331 if (queryResult == NULL)\r
332 {\r
333 ret = false;\r
334 break;\r
335 }\r
336\r
337 int rows = DBGetNumRows(queryResult);\r
338 g_paramGroup[i].values[dbIndex] = (DBParameter*)malloc(sizeof(DBParameter) * rows);\r
339 g_paramGroup[i].valueCount[dbIndex] = rows;\r
340 for (int j = 0; j < rows; j++)\r
341 {\r
342 TCHAR colname[MAX_STR];\r
343 DBGetField(queryResult, j, 0, (g_paramGroup[i].values[dbIndex])[j].name, MAX_STR);\r
344 (g_paramGroup[i].values[dbIndex])[j].attrs = new StringMap;\r
345 for (int k = 1; DBGetColumnName(queryResult, k, colname, MAX_STR); k++) \r
346 {\r
347 TCHAR colval[MAX_STR];\r
348 DBGetField(queryResult, j, k, colval, MAX_STR);\r
349 // AgentWriteDebugLog(9, _T("%s: getParamsFromDB: colname '%s' ::: colval '%s'"), MYNAMESTR, colname, colval);\r
350 (g_paramGroup[i].values[dbIndex])[j].attrs->set(colname, colval);\r
351 }\r
352 }\r
353\r
354 DBFreeResult(queryResult);\r
355 }\r
356\r
357 MutexUnlock(info.accessMutex);\r
358\r
359 return ret;\r
360}\r
361\r
362//\r
363// Subagent information\r
364//\r
365\r
366\r
367static NETXMS_SUBAGENT_PARAM m_parameters[] =\r
368{\r
369 { _T("Oracle.Sessions.Count(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/Sessions: Number of sessions opened") },\r
370 { _T("Oracle.Cursors.Count(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/Cursors: Current number of opened cursors systemwide") },\r
371 { _T("Oracle.DBInfo.Name(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database name") },\r
372 { _T("Oracle.DBInfo.CreateDate(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database creation date") },\r
373 { _T("Oracle.DBInfo.LogMode(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database log mode") },\r
374 { _T("Oracle.DBInfo.OpenMode(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Info: Database open mode") },\r
375 { _T("Oracle.TableSpaces.Status(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Tablespaces: Status") },\r
376 { _T("Oracle.TableSpaces.Type(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Tablespaces: Type") },\r
377 { _T("Oracle.TableSpaces.UsedPct(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/Tablespaces: Percentage used") },\r
378 { _T("Oracle.Instance.Version(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: DBMS Version") },\r
379 { _T("Oracle.Instance.Status(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: Status") },\r
380 { _T("Oracle.Instance.ArchiverStatus(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: Archiver status") },\r
381 { _T("Oracle.Instance.ShutdownPending(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/Instance: Is shutdown pending") },\r
382 { _T("Oracle.CriticalStats.TSOffCount(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/CriticalStats: Number of offline tablespaces") },\r
383 { _T("Oracle.CriticalStats.DFOffCount(*)"), getParameters, "X", DCI_DT_INT, _T("Oracle/CriticalStats: Number of offline datafiles") },\r
384 { _T("Oracle.CriticalStats.FullSegmentsCount(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of segments that cannot extend") },\r
385 { _T("Oracle.CriticalStats.RBSegsNotOnlineCount(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of rollback segments not online") },\r
386 { _T("Oracle.CriticalStats.AutoArchivingOff(*)"), getParameters, "X", DCI_DT_STRING, _T("Oracle/CriticalStats: Archive logs enabled but auto archiving off ") },\r
387 { _T("Oracle.CriticalStats.DatafilesNeedMediaRecovery(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of datafiles that need media recovery") },\r
388 { _T("Oracle.CriticalStats.FailedJobs(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/CriticalStats: Number of failed jobs") },\r
389 { _T("Oracle.Dual.ExcessRows(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/Dual: Excessive rows") },\r
390 { _T("Oracle.Performance.PhysReads(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/Performance: Number of physical reads") },\r
391 { _T("Oracle.Performance.LogicReads(*)"), getParameters, "X", DCI_DT_INT64, _T("Oracle/Performance: Number of logical reads") }\r
392};\r
393\r
394/*\r
395static NETXMS_SUBAGENT_ENUM m_enums[] =\r
396{\r
397};\r
398*/\r
399\r
400static NETXMS_SUBAGENT_INFO m_info =\r
401{\r
402 NETXMS_SUBAGENT_INFO_MAGIC,\r
403 _T("ORACLE"), NETXMS_VERSION_STRING,\r
404 SubAgentInit, SubAgentShutdown, NULL,\r
405 sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM), m_parameters,\r
406 0, NULL,\r
407 /*sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM),\r
408 m_parameters,\r
409 sizeof(m_enums) / sizeof(NETXMS_SUBAGENT_ENUM),\r
410 m_enums,*/\r
411 0, NULL\r
412};\r
413\r
414\r
415//\r
416// Entry point for NetXMS agent\r
417//\r
418\r
419DECLARE_SUBAGENT_ENTRY_POINT(ORACLE)\r
420{\r
421 *ppInfo = &m_info;\r
422 return TRUE;\r
423}\r
424\r
425\r
426//\r
427// DLL entry point\r
428//\r
429\r
430#ifdef _WIN32\r
431\r
432BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)\r
433{\r
434 if (dwReason == DLL_PROCESS_ATTACH)\r
435 DisableThreadLibraryCalls(hInstance);\r
436 return TRUE;\r
437}\r
438\r
439#endif\r