Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1257 lines
50 KiB

  1. /*++
  2. Copyright (c) 1997 Microsoft Corporation
  3. Module Name:
  4. perfjob.c
  5. Abstract:
  6. This file implements an Performance Job Object that presents
  7. information on the Job Object
  8. Created:
  9. Bob Watson 8-Oct-1997
  10. Revision History
  11. --*/
  12. //
  13. // Include Files
  14. //
  15. #include <nt.h>
  16. #include <ntrtl.h>
  17. #include <nturtl.h>
  18. #include <windows.h>
  19. #include <assert.h>
  20. #include <winperf.h>
  21. #include <ntprfctr.h>
  22. #include <perfutil.h>
  23. #include "perfsprc.h"
  24. #include "perfmsg.h"
  25. #include "procmsg.h"
  26. #include "datajob.h"
  27. #define MAX_STR_CHAR 1024
  28. #define MAX_STR_SIZE ((DWORD)((MAX_STR_CHAR - 1)* sizeof(WCHAR)))
  29. #define MAX_NAME_LENGTH MAX_PATH
  30. #define BUFFERSIZE 1024
  31. DWORD dwBufferSize = BUFFERSIZE;
  32. const WCHAR szJob[] = L"Job";
  33. const WCHAR szObjDirName[] = L"\\BaseNamedObjects";
  34. #define MAX_EVENT_STRINGS 4
  35. WORD wEvtStringCount;
  36. LPWSTR szEvtStringArray[MAX_EVENT_STRINGS];
  37. UNICODE_STRING DirectoryName = {(sizeof(szObjDirName) - sizeof(WCHAR)), // name len - NULL
  38. sizeof(szObjDirName), // size of buffer
  39. (PWCHAR)szObjDirName}; // address of buffer
  40. BOOL bOpenJobErrorLogged = FALSE;
  41. PSYSTEM_PROCESS_INFORMATION APIENTRY
  42. GetProcessPointerFromProcessId (
  43. IN ULONG_PTR dwPid
  44. )
  45. {
  46. PSYSTEM_PROCESS_INFORMATION ProcessInfo;
  47. ULONG ProcessBufferOffset = 0;
  48. BOOLEAN NullProcess;
  49. DWORD dwIndex = 0;
  50. BOOL bNotFound = TRUE;
  51. BOOL bMoreProcesses = FALSE;
  52. ProcessInfo = (PSYSTEM_PROCESS_INFORMATION) pProcessBuffer;
  53. if (ProcessInfo) {
  54. if (ProcessInfo->NextEntryOffset != 0) {
  55. bMoreProcesses = TRUE;
  56. }
  57. }
  58. while ( bMoreProcesses && bNotFound &&
  59. (ProcessInfo != NULL)) {
  60. // check for Live processes
  61. // (i.e. name or threads)
  62. if ((ProcessInfo->ImageName.Buffer != NULL) ||
  63. (ProcessInfo->NumberOfThreads > 0)){
  64. // thread is not Dead
  65. NullProcess = FALSE;
  66. } else {
  67. // thread is dead
  68. NullProcess = TRUE;
  69. }
  70. if (( !NullProcess ) && (dwPid == (HandleToUlong(ProcessInfo->UniqueProcessId)))) {
  71. // found it so return current value
  72. bNotFound = FALSE;
  73. continue;
  74. } else {
  75. dwIndex++;
  76. }
  77. // exit if this was the last process in list
  78. if (ProcessInfo->NextEntryOffset == 0) {
  79. bMoreProcesses = FALSE;
  80. continue;
  81. }
  82. // point to next buffer in list
  83. ProcessBufferOffset += ProcessInfo->NextEntryOffset;
  84. ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)
  85. &pProcessBuffer[ProcessBufferOffset];
  86. }
  87. if (bNotFound) {
  88. return NULL;
  89. } else {
  90. return ProcessInfo;
  91. }
  92. }
  93. DWORD APIENTRY
  94. CollectJobObjectData (
  95. IN OUT LPVOID *lppData,
  96. IN OUT LPDWORD lpcbTotalBytes,
  97. IN OUT LPDWORD lpNumObjectTypes
  98. )
  99. /*++
  100. Routine Description:
  101. This routine will return the data for the processor object
  102. Arguments:
  103. IN OUT LPVOID *lppData
  104. IN: pointer to the address of the buffer to receive the completed
  105. PerfDataBlock and subordinate structures. This routine will
  106. append its data to the buffer starting at the point referenced
  107. by *lppData.
  108. OUT: points to the first byte after the data structure added by this
  109. routine. This routine updated the value at lppdata after appending
  110. its data.
  111. IN OUT LPDWORD lpcbTotalBytes
  112. IN: the address of the DWORD that tells the size in bytes of the
  113. buffer referenced by the lppData argument
  114. OUT: the number of bytes added by this routine is writted to the
  115. DWORD pointed to by this argument
  116. IN OUT LPDWORD NumObjectTypes
  117. IN: the address of the DWORD to receive the number of objects added
  118. by this routine
  119. OUT: the number of objects added by this routine is writted to the
  120. DWORD pointed to by this argument
  121. Returns:
  122. 0 if successful, else Win 32 error code of failure
  123. --*/
  124. {
  125. DWORD TotalLen; // Length of the total return block
  126. PPERF_INSTANCE_DEFINITION pPerfInstanceDefinition;
  127. PJOB_DATA_DEFINITION pJobDataDefinition;
  128. PJOB_COUNTER_DATA pJCD;
  129. JOB_COUNTER_DATA jcdTotal;
  130. NTSTATUS Status = STATUS_SUCCESS;
  131. NTSTATUS tmpStatus = STATUS_SUCCESS;
  132. HANDLE DirectoryHandle, JobHandle;
  133. ULONG ReturnedLength;
  134. POBJECT_DIRECTORY_INFORMATION DirInfo;
  135. POBJECT_NAME_INFORMATION NameInfo;
  136. OBJECT_ATTRIBUTES Attributes;
  137. WCHAR wszNameBuffer[MAX_STR_CHAR];
  138. DWORD dwSize;
  139. PUCHAR Buffer;
  140. BOOL bStatus;
  141. JOBOBJECT_BASIC_ACCOUNTING_INFORMATION JobAcctInfo;
  142. DWORD dwWin32Status = ERROR_SUCCESS;
  143. ACCESS_MASK ExtraAccess = 0;
  144. ULONG Context = 0;
  145. DWORD NumJobInstances = 0;
  146. // get size of a data block that has 1 instance
  147. TotalLen = sizeof(JOB_DATA_DEFINITION) + // object def + counter defs
  148. sizeof (PERF_INSTANCE_DEFINITION) + // 1 instance def
  149. MAX_VALUE_NAME_LENGTH + // 1 instance name
  150. sizeof(JOB_COUNTER_DATA); // 1 instance data block
  151. if ( *lpcbTotalBytes < TotalLen ) {
  152. *lpcbTotalBytes = 0;
  153. *lpNumObjectTypes = 0;
  154. return ERROR_MORE_DATA;
  155. }
  156. // cast callers buffer to the object data definition type
  157. pJobDataDefinition = (JOB_DATA_DEFINITION *) *lppData;
  158. //
  159. // Define Job Object data block
  160. //
  161. memcpy(pJobDataDefinition,
  162. &JobDataDefinition,
  163. sizeof(JOB_DATA_DEFINITION));
  164. // set timestamp of this object
  165. pJobDataDefinition->JobObjectType.PerfTime = PerfTime;
  166. // Now collect data for each job object found in system
  167. //
  168. // Perform initial setup
  169. //
  170. Buffer = ALLOCMEM(hLibHeap, HEAP_ZERO_MEMORY, dwBufferSize);
  171. if ((Buffer == NULL)) {
  172. ReportEvent (hEventLog,
  173. EVENTLOG_ERROR_TYPE,
  174. 0,
  175. PERFPROC_UNABLE_ALLOCATE_JOB_DATA,
  176. NULL,
  177. 0,
  178. 0,
  179. NULL,
  180. NULL);
  181. *lpcbTotalBytes = 0;
  182. *lpNumObjectTypes = 0;
  183. return ERROR_SUCCESS;
  184. }
  185. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
  186. &pJobDataDefinition[1];
  187. // adjust TotalLen to be the size of the buffer already in use
  188. TotalLen = sizeof (JOB_DATA_DEFINITION);
  189. // zero the total instance buffer
  190. memset (&jcdTotal, 0, sizeof (jcdTotal));
  191. //
  192. // Open the directory for list directory access
  193. //
  194. // this should always succeed since it's a system name we
  195. // will be querying
  196. //
  197. InitializeObjectAttributes( &Attributes,
  198. &DirectoryName,
  199. OBJ_CASE_INSENSITIVE,
  200. NULL,
  201. NULL );
  202. Status = NtOpenDirectoryObject( &DirectoryHandle,
  203. DIRECTORY_QUERY | ExtraAccess,
  204. &Attributes
  205. );
  206. if (NT_SUCCESS( Status )) {
  207. //
  208. // Get the actual name of the object directory object.
  209. //
  210. NameInfo = (POBJECT_NAME_INFORMATION) &Buffer[0];
  211. Status = NtQueryObject( DirectoryHandle,
  212. ObjectNameInformation,
  213. NameInfo,
  214. dwBufferSize,
  215. (PULONG) NULL );
  216. }
  217. if (NT_SUCCESS( Status )) {
  218. //
  219. // Query the entire directory in one sweep
  220. //
  221. for (Status = NtQueryDirectoryObject( DirectoryHandle,
  222. Buffer,
  223. dwBufferSize,
  224. FALSE,
  225. FALSE,
  226. &Context,
  227. &ReturnedLength );
  228. NT_SUCCESS( Status );
  229. Status = NtQueryDirectoryObject( DirectoryHandle,
  230. Buffer,
  231. dwBufferSize,
  232. FALSE,
  233. FALSE,
  234. &Context,
  235. &ReturnedLength ) ) {
  236. //
  237. // Check the status of the operation.
  238. //
  239. if (!NT_SUCCESS( Status )) {
  240. break;
  241. }
  242. //
  243. // For every record in the buffer type out the directory information
  244. //
  245. //
  246. // Point to the first record in the buffer, we are guaranteed to have
  247. // one otherwise Status would have been No More Files
  248. //
  249. DirInfo = (POBJECT_DIRECTORY_INFORMATION) &Buffer[0];
  250. //
  251. // Continue while there's a valid record.
  252. //
  253. while (DirInfo->Name.Length != 0) {
  254. //
  255. // Print out information about the Job
  256. //
  257. if (wcsncmp ( DirInfo->TypeName.Buffer, &szJob[0], ((sizeof(szJob)/sizeof(WCHAR)) - 1)) == 0) {
  258. ULONG len;
  259. UNICODE_STRING JobName;
  260. NTSTATUS Status;
  261. // this is really a job, so list the name
  262. dwSize = DirInfo->Name.Length;
  263. if (dwSize > (MAX_STR_SIZE - sizeof(szObjDirName))) {
  264. dwSize = MAX_STR_SIZE - sizeof(szObjDirName);
  265. }
  266. len = wcslen(szObjDirName);
  267. wcscpy(wszNameBuffer, szObjDirName);
  268. wszNameBuffer[len] = L'\\';
  269. len++;
  270. memcpy (&wszNameBuffer[len], DirInfo->Name.Buffer, dwSize);
  271. wszNameBuffer[dwSize/sizeof(WCHAR)+len] = 0;
  272. // now query the process ID's for this job
  273. RtlInitUnicodeString(&JobName, wszNameBuffer);
  274. InitializeObjectAttributes(
  275. &Attributes,
  276. &JobName,
  277. 0,
  278. NULL, NULL);
  279. Status = NtOpenJobObject(
  280. &JobHandle,
  281. JOB_OBJECT_QUERY,
  282. &Attributes);
  283. if (NT_SUCCESS(Status)) {
  284. // strip Job name prefix for instance name
  285. memcpy (wszNameBuffer, DirInfo->Name.Buffer, dwSize);
  286. wszNameBuffer[dwSize/sizeof(WCHAR)] = 0;
  287. bStatus = QueryInformationJobObject (
  288. JobHandle,
  289. JobObjectBasicAccountingInformation,
  290. &JobAcctInfo,
  291. sizeof(JobAcctInfo),
  292. &ReturnedLength);
  293. ASSERT (ReturnedLength == sizeof(JobAcctInfo));
  294. if (bStatus) {
  295. // *** create and initialize perf data instance here ***
  296. // see if this instance will fit
  297. TotalLen += sizeof(PERF_INSTANCE_DEFINITION) +
  298. DWORD_MULTIPLE ((DirInfo->Name.Length + sizeof(WCHAR))) +
  299. sizeof (JOB_COUNTER_DATA);
  300. if ( *lpcbTotalBytes < TotalLen ) {
  301. *lpcbTotalBytes = 0;
  302. *lpNumObjectTypes = 0;
  303. Status = STATUS_NO_MEMORY;
  304. dwWin32Status = ERROR_MORE_DATA;
  305. break;
  306. }
  307. MonBuildInstanceDefinition(pPerfInstanceDefinition,
  308. (PVOID *) &pJCD,
  309. 0,
  310. 0,
  311. (DWORD)-1,
  312. wszNameBuffer);
  313. // test structure for Quadword Alignment
  314. assert (((DWORD)(pJCD) & 0x00000007) == 0);
  315. //
  316. // Format and collect Process data
  317. //
  318. pJCD->CounterBlock.ByteLength = sizeof (JOB_COUNTER_DATA);
  319. jcdTotal.CurrentProcessorTime +=
  320. pJCD->CurrentProcessorTime =
  321. JobAcctInfo.TotalUserTime.QuadPart +
  322. JobAcctInfo.TotalKernelTime.QuadPart;
  323. jcdTotal.CurrentUserTime +=
  324. pJCD->CurrentUserTime = JobAcctInfo.TotalUserTime.QuadPart;
  325. jcdTotal.CurrentKernelTime +=
  326. pJCD->CurrentKernelTime = JobAcctInfo.TotalKernelTime.QuadPart;
  327. #ifdef _DATAJOB_INCLUDE_TOTAL_COUNTERS
  328. // convert these times from 100 ns Time base to 1 mS time base
  329. jcdTotal.TotalProcessorTime +=
  330. pJCD->TotalProcessorTime =
  331. (JobAcctInfo.ThisPeriodTotalUserTime.QuadPart +
  332. JobAcctInfo.ThisPeriodTotalKernelTime.QuadPart) / 10000;
  333. jcdTotal.TotalUserTime +=
  334. pJCD->TotalUserTime =
  335. JobAcctInfo.ThisPeriodTotalUserTime.QuadPart / 10000;
  336. jcdTotal.TotalKernelTime +=
  337. pJCD->TotalKernelTime =
  338. JobAcctInfo.ThisPeriodTotalKernelTime.QuadPart / 1000;
  339. jcdTotal.CurrentProcessorUsage +=
  340. pJCD->CurrentProcessorUsage =
  341. (JobAcctInfo.TotalUserTime.QuadPart +
  342. JobAcctInfo.TotalKernelTime.QuadPart) / 10000;
  343. jcdTotal.CurrentUserUsage +=
  344. pJCD->CurrentUserUsage =
  345. JobAcctInfo.TotalUserTime.QuadPart / 10000;
  346. jcdTotal.CurrentKernelUsage +=
  347. pJCD->CurrentKernelUsage =
  348. JobAcctInfo.TotalKernelTime.QuadPart / 10000;
  349. #endif
  350. jcdTotal.PageFaults +=
  351. pJCD->PageFaults = JobAcctInfo.TotalPageFaultCount;
  352. jcdTotal.TotalProcessCount +=
  353. pJCD->TotalProcessCount = JobAcctInfo.TotalProcesses;
  354. jcdTotal.ActiveProcessCount +=
  355. pJCD->ActiveProcessCount = JobAcctInfo.ActiveProcesses;
  356. jcdTotal.TerminatedProcessCount +=
  357. pJCD->TerminatedProcessCount = JobAcctInfo.TotalTerminatedProcesses;
  358. NumJobInstances++;
  359. CloseHandle (JobHandle);
  360. // set perfdata pointer to next byte
  361. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pJCD[1];
  362. } else {
  363. // unable to query job accounting info
  364. dwWin32Status = GetLastError();
  365. tmpStatus = Status;
  366. Status = STATUS_SUCCESS;
  367. if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
  368. wEvtStringCount = 0;
  369. szEvtStringArray[wEvtStringCount++] = wszNameBuffer;
  370. // unable to open this Job
  371. ReportEventW (hEventLog,
  372. EVENTLOG_WARNING_TYPE,
  373. 0,
  374. PERFPROC_UNABLE_QUERY_JOB_ACCT,
  375. NULL,
  376. wEvtStringCount,
  377. sizeof(DWORD),
  378. szEvtStringArray,
  379. (LPVOID) & dwWin32Status);
  380. bOpenJobErrorLogged = TRUE;
  381. }
  382. }
  383. } else {
  384. dwWin32Status = GetLastError();
  385. tmpStatus = Status;
  386. Status = STATUS_SUCCESS;
  387. if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
  388. wEvtStringCount = 0;
  389. szEvtStringArray[wEvtStringCount++] = wszNameBuffer;
  390. // unable to open this Job
  391. ReportEventW (hEventLog,
  392. EVENTLOG_WARNING_TYPE,
  393. 0,
  394. PERFPROC_UNABLE_OPEN_JOB,
  395. NULL,
  396. wEvtStringCount,
  397. sizeof(DWORD),
  398. szEvtStringArray,
  399. (LPVOID) & dwWin32Status);
  400. bOpenJobErrorLogged = TRUE;
  401. }
  402. }
  403. }
  404. //
  405. // There is another record so advance DirInfo to the next entry
  406. //
  407. DirInfo = (POBJECT_DIRECTORY_INFORMATION) (((PUCHAR) DirInfo) +
  408. sizeof( OBJECT_DIRECTORY_INFORMATION ) );
  409. }
  410. RtlZeroMemory( Buffer, dwBufferSize );
  411. }
  412. if ((Status == STATUS_NO_MORE_FILES) ||
  413. (Status == STATUS_NO_MORE_ENTRIES)) {
  414. // this is OK
  415. Status = STATUS_SUCCESS;
  416. }
  417. if (Status == STATUS_SUCCESS && NumJobInstances == 0
  418. && bOpenJobErrorLogged == TRUE
  419. && dwWin32Status != ERROR_SUCCESS) {
  420. Status = tmpStatus;
  421. }
  422. if (Buffer) FREEMEM(hLibHeap, 0, Buffer);
  423. //
  424. // Now close the directory object
  425. //
  426. (VOID) NtClose( DirectoryHandle );
  427. }
  428. if (NT_SUCCESS(Status)) {
  429. if (NumJobInstances > 0) {
  430. // see if the total instance will fit
  431. TotalLen += sizeof(PERF_INSTANCE_DEFINITION) +
  432. (MAX_NAME_LENGTH+1+sizeof(DWORD))*
  433. sizeof(WCHAR) +
  434. sizeof (JOB_COUNTER_DATA);
  435. if ( *lpcbTotalBytes < TotalLen ) {
  436. *lpcbTotalBytes = 0;
  437. *lpNumObjectTypes = 0;
  438. return ERROR_MORE_DATA;
  439. }
  440. // it looks like it will fit so create "total" instance
  441. MonBuildInstanceDefinition(pPerfInstanceDefinition,
  442. (PVOID *) &pJCD,
  443. 0,
  444. 0,
  445. (DWORD)-1,
  446. wszTotal);
  447. // test structure for Quadword Alignment
  448. assert (((DWORD)(pJCD) & 0x00000007) == 0);
  449. //
  450. // transfer total info
  451. //
  452. memcpy (pJCD, &jcdTotal, sizeof (jcdTotal));
  453. pJCD->CounterBlock.ByteLength = sizeof (JOB_COUNTER_DATA);
  454. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pJCD[1];
  455. NumJobInstances++;
  456. }
  457. pJobDataDefinition->JobObjectType.NumInstances =
  458. NumJobInstances;
  459. //
  460. // Now we know how large an area we used for the
  461. // data, so we can update the offset
  462. // to the next object definition
  463. //
  464. *lpcbTotalBytes =
  465. pJobDataDefinition->JobObjectType.TotalByteLength =
  466. (DWORD)((PCHAR) pPerfInstanceDefinition -
  467. (PCHAR) pJobDataDefinition);
  468. #if DBG
  469. if (*lpcbTotalBytes > TotalLen ) {
  470. DbgPrint ("\nPERFPROC: Job Perf Ctr. Instance Size Underestimated:");
  471. DbgPrint ("\nPERFPROC: Estimated size: %d, Actual Size: %d", TotalLen, *lpcbTotalBytes);
  472. }
  473. #endif
  474. *lppData = (LPVOID) pPerfInstanceDefinition;
  475. *lpNumObjectTypes = 1;
  476. } else {
  477. *lpcbTotalBytes = 0;
  478. *lpNumObjectTypes = 0;
  479. if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
  480. wEvtStringCount = 0;
  481. szEvtStringArray[wEvtStringCount++] = DirectoryName.Buffer;
  482. // unable to query the object directory
  483. ReportEventW (hEventLog,
  484. EVENTLOG_WARNING_TYPE,
  485. 0,
  486. PERFPROC_UNABLE_QUERY_OBJECT_DIR,
  487. NULL,
  488. wEvtStringCount,
  489. sizeof(DWORD),
  490. szEvtStringArray,
  491. (LPVOID)&Status);
  492. bOpenJobErrorLogged = TRUE;
  493. }
  494. }
  495. return ERROR_SUCCESS;
  496. }
  497. DWORD APIENTRY
  498. CollectJobDetailData (
  499. IN OUT LPVOID *lppData,
  500. IN OUT LPDWORD lpcbTotalBytes,
  501. IN OUT LPDWORD lpNumObjectTypes
  502. )
  503. /*++
  504. Routine Description:
  505. This routine will return the data for the processor object
  506. Arguments:
  507. IN OUT LPVOID *lppData
  508. IN: pointer to the address of the buffer to receive the completed
  509. PerfDataBlock and subordinate structures. This routine will
  510. append its data to the buffer starting at the point referenced
  511. by *lppData.
  512. OUT: points to the first byte after the data structure added by this
  513. routine. This routine updated the value at lppdata after appending
  514. its data.
  515. IN OUT LPDWORD lpcbTotalBytes
  516. IN: the address of the DWORD that tells the size in bytes of the
  517. buffer referenced by the lppData argument
  518. OUT: the number of bytes added by this routine is writted to the
  519. DWORD pointed to by this argument
  520. IN OUT LPDWORD NumObjectTypes
  521. IN: the address of the DWORD to receive the number of objects added
  522. by this routine
  523. OUT: the number of objects added by this routine is writted to the
  524. DWORD pointed to by this argument
  525. Returns:
  526. 0 if successful, else Win 32 error code of failure
  527. --*/
  528. {
  529. PSYSTEM_PROCESS_INFORMATION ProcessInfo;
  530. PUNICODE_STRING pProcessName;
  531. DWORD TotalLen; // Length of the total return block
  532. PPERF_INSTANCE_DEFINITION pPerfInstanceDefinition;
  533. PJOB_DETAILS_DATA_DEFINITION pJobDetailsDataDefinition;
  534. PJOB_DETAILS_COUNTER_DATA pJDCD;
  535. JOB_DETAILS_COUNTER_DATA jdcdTotal;
  536. JOB_DETAILS_COUNTER_DATA jdcdGrandTotal;
  537. NTSTATUS Status = STATUS_SUCCESS;
  538. NTSTATUS tmpStatus = STATUS_SUCCESS;
  539. HANDLE DirectoryHandle, JobHandle;
  540. ULONG ReturnedLength;
  541. POBJECT_DIRECTORY_INFORMATION DirInfo;
  542. POBJECT_NAME_INFORMATION NameInfo;
  543. OBJECT_ATTRIBUTES Attributes;
  544. WCHAR wszNameBuffer[MAX_STR_CHAR];
  545. DWORD i, dwSize;
  546. PUCHAR Buffer;
  547. BOOL bStatus;
  548. PJOBOBJECT_BASIC_PROCESS_ID_LIST pJobPidList;
  549. DWORD dwWin32Status = ERROR_SUCCESS;
  550. ACCESS_MASK ExtraAccess = 0;
  551. ULONG Context = 0;
  552. DWORD NumJobObjects = 0;
  553. DWORD NumJobDetailInstances = 0;
  554. // get size of a data block that has 1 instance
  555. TotalLen = sizeof(JOB_DETAILS_DATA_DEFINITION) + // object def + counter defs
  556. sizeof (PERF_INSTANCE_DEFINITION) + // 1 instance def
  557. MAX_VALUE_NAME_LENGTH + // 1 instance name
  558. sizeof(JOB_DETAILS_COUNTER_DATA); // 1 instance data block
  559. if ( *lpcbTotalBytes < TotalLen ) {
  560. *lpcbTotalBytes = 0;
  561. *lpNumObjectTypes = 0;
  562. return ERROR_MORE_DATA;
  563. }
  564. // cast callers buffer to the object data definition type
  565. pJobDetailsDataDefinition = (JOB_DETAILS_DATA_DEFINITION *) *lppData;
  566. //
  567. // Define Job Details Object data block
  568. //
  569. memcpy(pJobDetailsDataDefinition,
  570. &JobDetailsDataDefinition,
  571. sizeof(JOB_DETAILS_DATA_DEFINITION));
  572. // set timestamp of this object
  573. pJobDetailsDataDefinition->JobDetailsObjectType.PerfTime = PerfTime;
  574. // Now collect data for each job object found in system
  575. //
  576. // Perform initial setup
  577. //
  578. Buffer = NULL;
  579. pJobPidList = NULL;
  580. if (hLibHeap) {
  581. Buffer = ALLOCMEM(hLibHeap, HEAP_ZERO_MEMORY, dwBufferSize);
  582. pJobPidList = ALLOCMEM(hLibHeap, HEAP_ZERO_MEMORY, dwBufferSize);
  583. }
  584. if ((Buffer == NULL) || (pJobPidList == NULL)) {
  585. *lpcbTotalBytes = 0;
  586. *lpNumObjectTypes = 0;
  587. // free the one that got allocated (if any)
  588. if (Buffer != NULL) FREEMEM(hLibHeap, 0, Buffer);
  589. if (pJobPidList != NULL) FREEMEM(hLibHeap, 0, pJobPidList);
  590. ReportEventW(hEventLog,
  591. EVENTLOG_ERROR_TYPE,
  592. 0,
  593. PERFPROC_UNABLE_ALLOCATE_JOB_DATA,
  594. NULL,
  595. 0,
  596. 0,
  597. szEvtStringArray,
  598. NULL);
  599. return ERROR_SUCCESS;
  600. }
  601. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
  602. &pJobDetailsDataDefinition[1];
  603. // adjust TotalLen to be the size of the buffer already in use
  604. TotalLen = sizeof (JOB_DETAILS_DATA_DEFINITION);
  605. // zero the total instance buffer
  606. memset (&jdcdGrandTotal, 0, sizeof (jdcdGrandTotal));
  607. //
  608. // Open the directory for list directory access
  609. //
  610. // this should always succeed since it's a system name we
  611. // will be querying
  612. //
  613. InitializeObjectAttributes( &Attributes,
  614. &DirectoryName,
  615. OBJ_CASE_INSENSITIVE,
  616. NULL,
  617. NULL );
  618. Status = NtOpenDirectoryObject( &DirectoryHandle,
  619. DIRECTORY_QUERY | ExtraAccess,
  620. &Attributes
  621. );
  622. if (NT_SUCCESS( Status )) {
  623. //
  624. // Get the actual name of the object directory object.
  625. //
  626. NameInfo = (POBJECT_NAME_INFORMATION) &Buffer[0];
  627. Status = NtQueryObject( DirectoryHandle,
  628. ObjectNameInformation,
  629. NameInfo,
  630. dwBufferSize,
  631. (PULONG) NULL );
  632. }
  633. if (NT_SUCCESS( Status )) {
  634. //
  635. // Query the entire directory in one sweep
  636. //
  637. for (Status = NtQueryDirectoryObject( DirectoryHandle,
  638. Buffer,
  639. dwBufferSize,
  640. FALSE,
  641. FALSE,
  642. &Context,
  643. &ReturnedLength );
  644. NT_SUCCESS( Status );
  645. Status = NtQueryDirectoryObject( DirectoryHandle,
  646. Buffer,
  647. dwBufferSize,
  648. FALSE,
  649. FALSE,
  650. &Context,
  651. &ReturnedLength ) ) {
  652. //
  653. // Check the status of the operation.
  654. //
  655. if (!NT_SUCCESS( Status )) {
  656. break;
  657. }
  658. //
  659. // For every record in the buffer type out the directory information
  660. //
  661. //
  662. // Point to the first record in the buffer, we are guaranteed to have
  663. // one otherwise Status would have been No More Files
  664. //
  665. DirInfo = (POBJECT_DIRECTORY_INFORMATION) &Buffer[0];
  666. //
  667. // contine while there's a valid record
  668. //
  669. while (DirInfo->Name.Length != 0) {
  670. //
  671. // Print out information about the Job
  672. //
  673. if (wcsncmp ( DirInfo->TypeName.Buffer, &szJob[0], ((sizeof(szJob)/sizeof(WCHAR)) - 1)) == 0) {
  674. ULONG len;
  675. UNICODE_STRING JobName;
  676. NTSTATUS Status;
  677. // this is really a job, so list the name
  678. dwSize = DirInfo->Name.Length;
  679. if (dwSize > (MAX_STR_SIZE - sizeof(szObjDirName))) {
  680. dwSize = MAX_STR_SIZE - sizeof(szObjDirName);
  681. }
  682. len = wcslen(szObjDirName);
  683. wcscpy(wszNameBuffer, szObjDirName);
  684. wszNameBuffer[len] = L'\\';
  685. len++;
  686. memcpy (&wszNameBuffer[len], DirInfo->Name.Buffer, dwSize);
  687. wszNameBuffer[dwSize/sizeof(WCHAR)+len] = 0;
  688. // now query the process ID's for this job
  689. RtlInitUnicodeString(&JobName, wszNameBuffer);
  690. InitializeObjectAttributes(
  691. &Attributes,
  692. &JobName,
  693. 0,
  694. NULL, NULL);
  695. Status = NtOpenJobObject(
  696. &JobHandle,
  697. JOB_OBJECT_QUERY,
  698. &Attributes);
  699. // clear the Job total counter block
  700. memset (&jdcdTotal, 0, sizeof (jdcdTotal));
  701. if (NT_SUCCESS(Status)) {
  702. // strip Job name prefix for instance name
  703. memcpy (wszNameBuffer, DirInfo->Name.Buffer, dwSize);
  704. wszNameBuffer[dwSize/sizeof(WCHAR)] = 0;
  705. // now query the process ID's for this job
  706. bStatus = QueryInformationJobObject (
  707. JobHandle,
  708. JobObjectBasicProcessIdList,
  709. pJobPidList,
  710. dwBufferSize,
  711. &ReturnedLength);
  712. // ASSERT (bStatus == TRUE);
  713. ASSERT (ReturnedLength <= BUFFERSIZE);
  714. ASSERT (pJobPidList->NumberOfAssignedProcesses ==
  715. pJobPidList->NumberOfProcessIdsInList);
  716. // test to see if there was enough room in the first buffer
  717. // for everything, if not, expand the buffer and retry
  718. if ((bStatus) && (pJobPidList->NumberOfAssignedProcesses >
  719. pJobPidList->NumberOfProcessIdsInList)) {
  720. PJOBOBJECT_BASIC_PROCESS_ID_LIST pOldBuffer;
  721. dwBufferSize +=
  722. (pJobPidList->NumberOfAssignedProcesses -
  723. pJobPidList->NumberOfProcessIdsInList) *
  724. sizeof (DWORD);
  725. pOldBuffer = pJobPidList;
  726. pJobPidList = REALLOCMEM (hLibHeap, 0,
  727. pJobPidList, dwBufferSize);
  728. // ASSERT (pJobPidList != NULL);
  729. if (pJobPidList != NULL) {
  730. bStatus = QueryInformationJobObject (
  731. JobHandle,
  732. JobObjectBasicProcessIdList,
  733. pJobPidList,
  734. dwBufferSize,
  735. &ReturnedLength);
  736. } else {
  737. bStatus = FALSE;
  738. FREEMEM(hLibHeap, 0, pOldBuffer);
  739. SetLastError ( ERROR_OUTOFMEMORY );
  740. }
  741. }
  742. if (bStatus) {
  743. for (i=0;i < pJobPidList->NumberOfProcessIdsInList; i++) {
  744. // *** create and initialize perf data instance here ***
  745. // get process data object from ID
  746. ProcessInfo = GetProcessPointerFromProcessId (pJobPidList->ProcessIdList[i]);
  747. // ASSERT (ProcessInfo != NULL);
  748. if (ProcessInfo != NULL) {
  749. // get process name
  750. if (lProcessNameCollectionMethod == PNCM_MODULE_FILE) {
  751. pProcessName = GetProcessSlowName (ProcessInfo);
  752. } else {
  753. pProcessName = GetProcessShortName (ProcessInfo);
  754. }
  755. ReturnedLength = pProcessName->Length + sizeof(WCHAR);
  756. // see if this instance will fit
  757. TotalLen += sizeof(PERF_INSTANCE_DEFINITION) +
  758. DWORD_MULTIPLE (ReturnedLength) +
  759. sizeof (JOB_DETAILS_COUNTER_DATA);
  760. if ( *lpcbTotalBytes < TotalLen ) {
  761. *lpcbTotalBytes = 0;
  762. *lpNumObjectTypes = 0;
  763. Status = STATUS_NO_MEMORY;
  764. dwWin32Status = ERROR_MORE_DATA;
  765. break;
  766. }
  767. MonBuildInstanceDefinition(pPerfInstanceDefinition,
  768. (PVOID *) &pJDCD,
  769. JOB_OBJECT_TITLE_INDEX,
  770. NumJobObjects,
  771. (DWORD)-1,
  772. pProcessName->Buffer);
  773. // test structure for Quadword Alignment
  774. assert (((DWORD)(pJDCD) & 0x00000007) == 0);
  775. //
  776. // Format and collect Process data
  777. //
  778. pJDCD->CounterBlock.ByteLength = sizeof (JOB_DETAILS_COUNTER_DATA);
  779. //
  780. // Convert User time from 100 nsec units to counter frequency.
  781. //
  782. jdcdTotal.ProcessorTime +=
  783. pJDCD->ProcessorTime = ProcessInfo->KernelTime.QuadPart +
  784. ProcessInfo->UserTime.QuadPart;
  785. jdcdTotal.UserTime +=
  786. pJDCD->UserTime = ProcessInfo->UserTime.QuadPart;
  787. jdcdTotal.KernelTime +=
  788. pJDCD->KernelTime = ProcessInfo->KernelTime.QuadPart;
  789. jdcdTotal.PeakVirtualSize +=
  790. pJDCD->PeakVirtualSize = ProcessInfo->PeakVirtualSize;
  791. jdcdTotal.VirtualSize +=
  792. pJDCD->VirtualSize = ProcessInfo->VirtualSize;
  793. jdcdTotal.PageFaults +=
  794. pJDCD->PageFaults = ProcessInfo->PageFaultCount;
  795. jdcdTotal.PeakWorkingSet +=
  796. pJDCD->PeakWorkingSet = ProcessInfo->PeakWorkingSetSize;
  797. jdcdTotal.TotalWorkingSet +=
  798. pJDCD->TotalWorkingSet = ProcessInfo->WorkingSetSize;
  799. #ifdef _DATAPROC_PRIVATE_WS_
  800. jdcdTotal.PrivateWorkingSet +=
  801. pJDCD->PrivateWorkingSet = ProcessInfo->PrivateWorkingSetSize;
  802. jdcdTotal.SharedWorkingSet +=
  803. pJDCD->SharedWorkingSet =
  804. ProcessInfo->WorkingSetSize -
  805. ProcessInfo->PrivateWorkingSetSize;
  806. #endif
  807. jdcdTotal.PeakPageFile +=
  808. pJDCD->PeakPageFile = ProcessInfo->PeakPagefileUsage;
  809. jdcdTotal.PageFile +=
  810. pJDCD->PageFile = ProcessInfo->PagefileUsage;
  811. jdcdTotal.PrivatePages +=
  812. pJDCD->PrivatePages = ProcessInfo->PrivatePageCount;
  813. jdcdTotal.ThreadCount +=
  814. pJDCD->ThreadCount = ProcessInfo->NumberOfThreads;
  815. // base priority is not totaled
  816. pJDCD->BasePriority = ProcessInfo->BasePriority;
  817. // elpased time is not totaled
  818. pJDCD->ElapsedTime = ProcessInfo->CreateTime.QuadPart;
  819. pJDCD->ProcessId = HandleToUlong(ProcessInfo->UniqueProcessId);
  820. pJDCD->CreatorProcessId = HandleToUlong(ProcessInfo->InheritedFromUniqueProcessId);
  821. jdcdTotal.PagedPool +=
  822. pJDCD->PagedPool = (DWORD)ProcessInfo->QuotaPagedPoolUsage;
  823. jdcdTotal.NonPagedPool +=
  824. pJDCD->NonPagedPool = (DWORD)ProcessInfo->QuotaNonPagedPoolUsage;
  825. jdcdTotal.HandleCount +=
  826. pJDCD->HandleCount = (DWORD)ProcessInfo->HandleCount;
  827. // update I/O counters
  828. jdcdTotal.ReadOperationCount +=
  829. pJDCD->ReadOperationCount = ProcessInfo->ReadOperationCount.QuadPart;
  830. jdcdTotal.DataOperationCount +=
  831. pJDCD->DataOperationCount = ProcessInfo->ReadOperationCount.QuadPart;
  832. jdcdTotal.WriteOperationCount +=
  833. pJDCD->WriteOperationCount = ProcessInfo->WriteOperationCount.QuadPart;
  834. jdcdTotal.DataOperationCount += ProcessInfo->WriteOperationCount.QuadPart;
  835. pJDCD->DataOperationCount += ProcessInfo->WriteOperationCount.QuadPart;
  836. jdcdTotal.OtherOperationCount +=
  837. pJDCD->OtherOperationCount = ProcessInfo->OtherOperationCount.QuadPart;
  838. jdcdTotal.ReadTransferCount +=
  839. pJDCD->ReadTransferCount = ProcessInfo->ReadTransferCount.QuadPart;
  840. jdcdTotal.DataTransferCount +=
  841. pJDCD->DataTransferCount = ProcessInfo->ReadTransferCount.QuadPart;
  842. jdcdTotal.WriteTransferCount +=
  843. pJDCD->WriteTransferCount = ProcessInfo->WriteTransferCount.QuadPart;
  844. jdcdTotal.DataTransferCount += ProcessInfo->WriteTransferCount.QuadPart;
  845. pJDCD->DataTransferCount += ProcessInfo->WriteTransferCount.QuadPart;
  846. jdcdTotal.OtherTransferCount +=
  847. pJDCD->OtherTransferCount = ProcessInfo->OtherTransferCount.QuadPart;
  848. // set perfdata pointer to next byte
  849. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pJDCD[1];
  850. NumJobDetailInstances++;
  851. } else {
  852. // unable to locate info on this process
  853. // for now, we'll ignore this...
  854. }
  855. }
  856. CloseHandle (JobHandle);
  857. // see if this instance will fit
  858. TotalLen += sizeof(PERF_INSTANCE_DEFINITION) +
  859. DWORD_MULTIPLE (MAX_STR_SIZE) +
  860. sizeof (JOB_DETAILS_COUNTER_DATA);
  861. if ( *lpcbTotalBytes < TotalLen ) {
  862. *lpcbTotalBytes = 0;
  863. *lpNumObjectTypes = 0;
  864. Status = STATUS_NO_MEMORY;
  865. dwWin32Status = ERROR_MORE_DATA;
  866. break;
  867. }
  868. MonBuildInstanceDefinition(pPerfInstanceDefinition,
  869. (PVOID *) &pJDCD,
  870. JOB_OBJECT_TITLE_INDEX,
  871. NumJobObjects,
  872. (DWORD)-1,
  873. wszTotal);
  874. // test structure for Quadword Alignment
  875. assert (((DWORD)(pJDCD) & 0x00000007) == 0);
  876. // copy total data to caller's buffer
  877. memcpy (pJDCD, &jdcdTotal, sizeof (jdcdTotal));
  878. pJDCD->CounterBlock.ByteLength = sizeof (JOB_DETAILS_COUNTER_DATA);
  879. // update grand total instance
  880. //
  881. jdcdGrandTotal.ProcessorTime += jdcdTotal.ProcessorTime;
  882. jdcdGrandTotal.UserTime += jdcdTotal.UserTime;
  883. jdcdGrandTotal.KernelTime += jdcdTotal. KernelTime;
  884. jdcdGrandTotal.PeakVirtualSize += jdcdTotal.PeakVirtualSize;
  885. jdcdGrandTotal.VirtualSize += jdcdTotal.VirtualSize;
  886. jdcdGrandTotal.PageFaults += jdcdTotal.PageFaults;
  887. jdcdGrandTotal.PeakWorkingSet += jdcdTotal.PeakWorkingSet;
  888. jdcdGrandTotal.TotalWorkingSet += jdcdTotal.TotalWorkingSet;
  889. #ifdef _DATAPROC_PRIVATE_WS_
  890. jdcdGrandTotal.PrivateWorkingSet += jdcdTotal.PrivateWorkingSet;
  891. jdcdGrandTotal.SharedWorkingSet += jdcdTotal.SharedWorkingSet;
  892. #endif
  893. jdcdGrandTotal.PeakPageFile += jdcdTotal.PeakPageFile;
  894. jdcdGrandTotal.PageFile += jdcdTotal.PageFile;
  895. jdcdGrandTotal.PrivatePages += jdcdTotal.PrivatePages;
  896. jdcdGrandTotal.ThreadCount += jdcdTotal.ThreadCount;
  897. jdcdGrandTotal.PagedPool += jdcdTotal.PagedPool;
  898. jdcdGrandTotal.NonPagedPool += jdcdTotal.NonPagedPool;
  899. jdcdGrandTotal.HandleCount += jdcdTotal.HandleCount;
  900. // set perfdata pointer to next byte
  901. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pJDCD[1];
  902. NumJobDetailInstances++;
  903. NumJobObjects++;
  904. } else {
  905. // unable to read PID list from Job
  906. dwWin32Status = GetLastError();
  907. tmpStatus = Status;
  908. Status = STATUS_SUCCESS;
  909. if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
  910. wEvtStringCount = 0;
  911. szEvtStringArray[wEvtStringCount++] = wszNameBuffer;
  912. // unable to open this Job
  913. ReportEventW (hEventLog,
  914. EVENTLOG_WARNING_TYPE,
  915. 0,
  916. PERFPROC_UNABLE_QUERY_JOB_PIDS,
  917. NULL,
  918. wEvtStringCount,
  919. sizeof(DWORD),
  920. szEvtStringArray,
  921. (LPVOID) & dwWin32Status);
  922. bOpenJobErrorLogged = TRUE;
  923. }
  924. }
  925. } else {
  926. dwWin32Status = GetLastError();
  927. tmpStatus = Status;
  928. Status = STATUS_SUCCESS;
  929. if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
  930. wEvtStringCount = 0;
  931. szEvtStringArray[wEvtStringCount++] = wszNameBuffer;
  932. // unable to open this Job
  933. ReportEventW (hEventLog,
  934. EVENTLOG_WARNING_TYPE,
  935. 0,
  936. PERFPROC_UNABLE_OPEN_JOB,
  937. NULL,
  938. wEvtStringCount,
  939. sizeof(DWORD),
  940. szEvtStringArray,
  941. (LPVOID) & dwWin32Status);
  942. bOpenJobErrorLogged = TRUE;
  943. }
  944. }
  945. }
  946. //
  947. // There is another record so advance DirInfo to the next entry
  948. //
  949. DirInfo = (POBJECT_DIRECTORY_INFORMATION) (((PUCHAR) DirInfo) +
  950. sizeof( OBJECT_DIRECTORY_INFORMATION ) );
  951. }
  952. RtlZeroMemory( Buffer, dwBufferSize );
  953. }
  954. if ((Status == STATUS_NO_MORE_FILES) ||
  955. (Status == STATUS_NO_MORE_ENTRIES)) {
  956. // this is OK
  957. Status = STATUS_SUCCESS;
  958. }
  959. if (Status == STATUS_SUCCESS && NumJobDetailInstances == 0
  960. && bOpenJobErrorLogged == TRUE
  961. && dwWin32Status != ERROR_SUCCESS) {
  962. Status = tmpStatus;
  963. }
  964. if (Buffer) FREEMEM(hLibHeap, 0, Buffer);
  965. if (pJobPidList) FREEMEM(hLibHeap, 0, pJobPidList);
  966. //
  967. // Now close the directory object
  968. //
  969. (VOID) NtClose( DirectoryHandle );
  970. if (NumJobDetailInstances > 0) {
  971. // see if this instance will fit
  972. TotalLen += sizeof(PERF_INSTANCE_DEFINITION) +
  973. DWORD_MULTIPLE (MAX_STR_SIZE) +
  974. sizeof (JOB_DETAILS_COUNTER_DATA);
  975. if ( *lpcbTotalBytes < TotalLen ) {
  976. *lpcbTotalBytes = 0;
  977. *lpNumObjectTypes = 0;
  978. Status = STATUS_NO_MEMORY;
  979. dwWin32Status = ERROR_MORE_DATA;
  980. } else {
  981. // set the Total Elapsed Time to be the current time so that it will
  982. // show up as 0 when displayed.
  983. jdcdGrandTotal.ElapsedTime = pJobDetailsDataDefinition->JobDetailsObjectType.PerfTime.QuadPart;
  984. // build the grand total instance
  985. MonBuildInstanceDefinition(pPerfInstanceDefinition,
  986. (PVOID *) &pJDCD,
  987. JOB_OBJECT_TITLE_INDEX,
  988. NumJobObjects,
  989. (DWORD)-1,
  990. wszTotal);
  991. // test structure for Quadword Alignment
  992. ASSERT (((ULONG_PTR)(pJDCD) & 0x00000007) == 0);
  993. // copy total data to caller's buffer
  994. memcpy (pJDCD, &jdcdGrandTotal, sizeof (jdcdGrandTotal));
  995. pJDCD->CounterBlock.ByteLength = sizeof (JOB_DETAILS_COUNTER_DATA);
  996. // update pointers
  997. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pJDCD[1];
  998. NumJobDetailInstances++;
  999. }
  1000. }
  1001. pJobDetailsDataDefinition->JobDetailsObjectType.NumInstances =
  1002. NumJobDetailInstances;
  1003. //
  1004. // Now we know how large an area we used for the
  1005. // Process definition, so we can update the offset
  1006. // to the next object definition
  1007. //
  1008. *lpcbTotalBytes =
  1009. pJobDetailsDataDefinition->JobDetailsObjectType.TotalByteLength =
  1010. (DWORD)((PCHAR) pPerfInstanceDefinition -
  1011. (PCHAR) pJobDetailsDataDefinition);
  1012. #if DBG
  1013. if (*lpcbTotalBytes > TotalLen ) {
  1014. DbgPrint ("\nPERFPROC: Job Perf Ctr. Instance Size Underestimated:");
  1015. DbgPrint ("\nPERFPROC: Estimated size: %d, Actual Size: %d", TotalLen, *lpcbTotalBytes);
  1016. }
  1017. #endif
  1018. *lppData = (LPVOID) pPerfInstanceDefinition;
  1019. *lpNumObjectTypes = 1;
  1020. } else {
  1021. *lpcbTotalBytes = 0;
  1022. *lpNumObjectTypes = 0;
  1023. if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
  1024. wEvtStringCount = 0;
  1025. szEvtStringArray[wEvtStringCount++] = DirectoryName.Buffer;
  1026. // unable to query the object directory
  1027. ReportEventW (hEventLog,
  1028. EVENTLOG_WARNING_TYPE,
  1029. 0,
  1030. PERFPROC_UNABLE_QUERY_OBJECT_DIR,
  1031. NULL,
  1032. wEvtStringCount,
  1033. sizeof(DWORD),
  1034. szEvtStringArray,
  1035. (LPVOID)&Status);
  1036. bOpenJobErrorLogged = TRUE;
  1037. }
  1038. }
  1039. return ERROR_SUCCESS;
  1040. }