Leaked source code of windows server 2003
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.

733 lines
20 KiB

  1. /*++
  2. Copyright (c) 1990 Microsoft Corporation
  3. Module Name:
  4. CalcPerf.c
  5. Abstract:
  6. calculate perfoemance statistics
  7. Author:
  8. Environment:
  9. Win32
  10. Revision History:
  11. 10-20-91 Initial version
  12. --*/
  13. #include <nt.h>
  14. #include <ntrtl.h>
  15. #include <nturtl.h>
  16. #include <windows.h>
  17. #include <assert.h>
  18. #include "calcperf.h"
  19. SYSTEM_EXCEPTION_INFORMATION ExceptionInfo;
  20. SYSTEM_EXCEPTION_INFORMATION PreviousExceptionInfo;
  21. SYSTEM_PERFORMANCE_INFORMATION PerfInfo;
  22. SYSTEM_PERFORMANCE_INFORMATION PreviousPerfInfo;
  23. POBJECT_TYPE_INFORMATION ObjectInfo;
  24. WCHAR Buffer[ 256 ];
  25. STRING DeviceName;
  26. UNICODE_STRING DeviceNameU;
  27. OBJECT_ATTRIBUTES ObjectAttributes;
  28. NTSTATUS Status;
  29. CCHAR NumberOfProcessors;
  30. SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION ProcessorInfo[MAX_PROCESSOR];
  31. CPU_VALUE PreviousCpuData[MAX_PROCESSOR];
  32. //
  33. // make the maximum for pages available a "grow only" max. (since the
  34. // amount of memory in a machine is limited. Set to 1 Mbytes here.
  35. //
  36. ULONG PgAvailMax = 16384;
  37. ULONG PreviousInterruptCount;
  38. ULONG InterruptCount;
  39. ULONG
  40. InitPerfInfo(
  41. VOID
  42. )
  43. /*++
  44. Routine Description:
  45. Initialize data for perf measurements
  46. Arguments:
  47. None
  48. Return Value:
  49. Number of system processors (0 if error)
  50. Revision History:
  51. 10-21-91 Initial code
  52. --*/
  53. {
  54. SYSTEM_BASIC_INFORMATION BasicInfo;
  55. PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION PPerfInfo;
  56. int i;
  57. //
  58. // Init Nt performance interface
  59. //
  60. NtQuerySystemInformation(
  61. SystemExceptionInformation,
  62. &ExceptionInfo,
  63. sizeof(ExceptionInfo),
  64. NULL
  65. );
  66. PreviousExceptionInfo = ExceptionInfo;
  67. NtQuerySystemInformation(
  68. SystemPerformanceInformation,
  69. &PerfInfo,
  70. sizeof(PerfInfo),
  71. NULL
  72. );
  73. PreviousPerfInfo = PerfInfo;
  74. NtQuerySystemInformation(
  75. SystemBasicInformation,
  76. &BasicInfo,
  77. sizeof(BasicInfo),
  78. NULL
  79. );
  80. NumberOfProcessors = BasicInfo.NumberOfProcessors;
  81. if (NumberOfProcessors > MAX_PROCESSOR) {
  82. return(0);
  83. }
  84. NtQuerySystemInformation(
  85. SystemProcessorPerformanceInformation,
  86. ProcessorInfo,
  87. sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * MAX_PROCESSOR,
  88. NULL
  89. );
  90. PPerfInfo = ProcessorInfo;
  91. PreviousInterruptCount = 0;
  92. for (i=0; i < NumberOfProcessors; i++) {
  93. PreviousInterruptCount += PPerfInfo->InterruptCount;
  94. PreviousCpuData[i].KernelTime = PPerfInfo->KernelTime;
  95. PreviousCpuData[i].UserTime = PPerfInfo->UserTime;
  96. PreviousCpuData[i].IdleTime = PPerfInfo->IdleTime;
  97. PreviousCpuData[i].DpcTime = PPerfInfo->DpcTime;
  98. PreviousCpuData[i].InterruptTime = PPerfInfo->InterruptTime;
  99. PreviousCpuData[i].InterruptCount = PPerfInfo->InterruptCount;
  100. PPerfInfo++;
  101. }
  102. return(NumberOfProcessors);
  103. }
  104. BOOL
  105. CalcCpuTime(
  106. PDISPLAY_ITEM PerfListItem
  107. )
  108. /*++
  109. Routine Description:
  110. calculate and return %cpu time and time periods
  111. Arguments:
  112. None
  113. Return Value:
  114. Revision History:
  115. 10-21-91 Initial code
  116. --*/
  117. {
  118. LARGE_INTEGER CurrentTime;
  119. LARGE_INTEGER PreviousTime;
  120. LARGE_INTEGER ElapsedTime;
  121. LARGE_INTEGER ElapsedSystemTime;
  122. LARGE_INTEGER PercentTime;
  123. LARGE_INTEGER DeltaKernelTime,DeltaUserTime,DeltaIdleTime;
  124. LARGE_INTEGER DeltaInterruptTime,DeltaDpcTime;
  125. LARGE_INTEGER TotalElapsedTime;
  126. LARGE_INTEGER TotalKernelTime;
  127. LARGE_INTEGER TotalUserTime;
  128. LARGE_INTEGER TotalIdleTime;
  129. LARGE_INTEGER TotalDpcTime;
  130. LARGE_INTEGER TotalInterruptTime;
  131. ULONG ProcessCount, ThreadCount;
  132. ULONG ListIndex;
  133. ULONG Total;
  134. // PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION PPerfInfo;
  135. //
  136. // get system performance info
  137. //
  138. NtQuerySystemInformation(
  139. SystemExceptionInformation,
  140. &ExceptionInfo,
  141. sizeof(ExceptionInfo),
  142. NULL
  143. );
  144. NtQuerySystemInformation(
  145. SystemPerformanceInformation,
  146. &PerfInfo,
  147. sizeof(PerfInfo),
  148. NULL
  149. );
  150. NtQuerySystemInformation(
  151. SystemProcessorPerformanceInformation,
  152. ProcessorInfo,
  153. sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * MAX_PROCESSOR,
  154. NULL
  155. );
  156. ObjectInfo = (POBJECT_TYPE_INFORMATION)Buffer;
  157. NtQueryObject( NtCurrentProcess(),
  158. ObjectTypeInformation,
  159. ObjectInfo,
  160. sizeof( Buffer ),
  161. NULL
  162. );
  163. ProcessCount = ObjectInfo->TotalNumberOfObjects;
  164. NtQueryObject( NtCurrentThread(),
  165. ObjectTypeInformation,
  166. ObjectInfo,
  167. sizeof( Buffer ),
  168. NULL
  169. );
  170. ThreadCount = ObjectInfo->TotalNumberOfObjects;
  171. //
  172. // calculate Kernel,User,Total times for each CPU.
  173. // SUM the interrupt count accross all CPUs
  174. //
  175. InterruptCount = 0;
  176. TotalElapsedTime.QuadPart = 0;
  177. TotalKernelTime = TotalElapsedTime;
  178. TotalUserTime = TotalElapsedTime;
  179. TotalIdleTime = TotalElapsedTime;
  180. TotalInterruptTime = TotalElapsedTime;
  181. TotalDpcTime = TotalElapsedTime;
  182. for (ListIndex=0;ListIndex<MAX_PROCESSOR;ListIndex++) {
  183. //
  184. // Increment the interrupt count for each processor
  185. //
  186. InterruptCount += ProcessorInfo[ListIndex].InterruptCount;
  187. //
  188. // Calculate the % kernel,user,total for each CPU.
  189. //
  190. // Note that DPC and Interrupt time are charged against kernel time
  191. // already.
  192. //
  193. PreviousTime.QuadPart = PreviousCpuData[ListIndex].KernelTime.QuadPart+
  194. PreviousCpuData[ListIndex].UserTime.QuadPart;
  195. CurrentTime.QuadPart = ProcessorInfo[ListIndex].KernelTime.QuadPart+
  196. ProcessorInfo[ListIndex].UserTime.QuadPart;
  197. ElapsedSystemTime.QuadPart = CurrentTime.QuadPart - PreviousTime.QuadPart;
  198. //
  199. // UserTime = (User) *100
  200. // ----------------
  201. // Kernel + User
  202. //
  203. //
  204. // Idle *100
  205. // TotalTime = 100 - --------------
  206. // Kernel + User
  207. //
  208. //
  209. //
  210. // (Kernel - Idle - DPC - Interrupt)*100
  211. // KernelTime = -------------------
  212. // Kernel + User
  213. //
  214. DeltaUserTime.QuadPart = ProcessorInfo[ListIndex].UserTime.QuadPart -
  215. PreviousCpuData[ListIndex].UserTime.QuadPart;
  216. DeltaIdleTime.QuadPart = ProcessorInfo[ListIndex].IdleTime.QuadPart -
  217. PreviousCpuData[ListIndex].IdleTime.QuadPart;
  218. DeltaDpcTime.QuadPart = ProcessorInfo[ListIndex].DpcTime.QuadPart -
  219. PreviousCpuData[ListIndex].DpcTime.QuadPart;
  220. DeltaInterruptTime.QuadPart = ProcessorInfo[ListIndex].InterruptTime.QuadPart -
  221. PreviousCpuData[ListIndex].InterruptTime.QuadPart;
  222. DeltaKernelTime.QuadPart = ProcessorInfo[ListIndex].KernelTime.QuadPart -
  223. PreviousCpuData[ListIndex].KernelTime.QuadPart;
  224. DeltaKernelTime.QuadPart = DeltaKernelTime.QuadPart -
  225. DeltaIdleTime.QuadPart -
  226. DeltaDpcTime.QuadPart -
  227. DeltaInterruptTime.QuadPart;
  228. //
  229. // accumulate per CPU information for the Total CPU field
  230. //
  231. TotalElapsedTime.QuadPart += ElapsedSystemTime.QuadPart;
  232. TotalIdleTime.QuadPart += DeltaIdleTime.QuadPart;
  233. TotalUserTime.QuadPart += DeltaUserTime.QuadPart;
  234. TotalKernelTime.QuadPart += DeltaKernelTime.QuadPart;
  235. TotalDpcTime.QuadPart += DeltaDpcTime.QuadPart;
  236. TotalInterruptTime.QuadPart += DeltaInterruptTime.QuadPart;
  237. //
  238. // Update old time value entries
  239. //
  240. PreviousCpuData[ListIndex].UserTime = ProcessorInfo[ListIndex].UserTime;
  241. PreviousCpuData[ListIndex].KernelTime = ProcessorInfo[ListIndex].KernelTime;
  242. PreviousCpuData[ListIndex].IdleTime = ProcessorInfo[ListIndex].IdleTime;
  243. PreviousCpuData[ListIndex].DpcTime = ProcessorInfo[ListIndex].DpcTime;
  244. PreviousCpuData[ListIndex].InterruptTime= ProcessorInfo[ListIndex].InterruptTime;
  245. //
  246. // If the elapsed system time is not zero, then compute the percentage
  247. // of time spent in user, kernel, DPC, and interupt mode. Otherwise, default the time
  248. // to zero.
  249. //
  250. if (ElapsedSystemTime.QuadPart != 0) {
  251. //
  252. // Calculate User Time %
  253. //
  254. ElapsedTime.QuadPart = DeltaUserTime.QuadPart * 100;
  255. PercentTime.QuadPart = ElapsedTime.QuadPart / ElapsedSystemTime.QuadPart;
  256. //
  257. // Save User Time
  258. //
  259. UpdatePerfInfo(&PerfListItem[ListIndex].UserTime[0],PercentTime.LowPart,NULL);
  260. //
  261. // Calculate Total Cpu time
  262. //
  263. ElapsedTime.QuadPart = DeltaIdleTime.QuadPart*100;
  264. PercentTime.QuadPart = ElapsedTime.QuadPart / ElapsedSystemTime.QuadPart;
  265. //
  266. // Save Total Time
  267. //
  268. Total = 100 - PercentTime.LowPart;
  269. if (Total > 100) {
  270. Total = 100;
  271. }
  272. UpdatePerfInfo(&PerfListItem[ListIndex].TotalTime[0],Total,NULL);
  273. //
  274. // Calculate Kernel Time %
  275. //
  276. ElapsedTime.QuadPart = DeltaKernelTime.QuadPart * 100;
  277. PercentTime.QuadPart = ElapsedTime.QuadPart / ElapsedSystemTime.QuadPart;
  278. //
  279. // Save Kernel Time
  280. //
  281. UpdatePerfInfo(&PerfListItem[ListIndex].KernelTime[0],PercentTime.LowPart,NULL);
  282. //
  283. // Calculate DPC Time %
  284. //
  285. ElapsedTime.QuadPart = DeltaDpcTime.QuadPart * 100;
  286. PercentTime.QuadPart = ElapsedTime.QuadPart / ElapsedSystemTime.QuadPart;
  287. //
  288. // Save DPC Time
  289. //
  290. UpdatePerfInfo(&PerfListItem[ListIndex].DpcTime[0],PercentTime.LowPart,NULL);
  291. //
  292. // Calculate Interrupt Time %
  293. //
  294. ElapsedTime.QuadPart = DeltaInterruptTime.QuadPart * 100;
  295. PercentTime.QuadPart = ElapsedTime.QuadPart / ElapsedSystemTime.QuadPart;
  296. //
  297. // Save DPC Time
  298. //
  299. UpdatePerfInfo(&PerfListItem[ListIndex].InterruptTime[0],PercentTime.LowPart,NULL);
  300. } else {
  301. //
  302. // Set percentage of user and kernel time to zero.
  303. //
  304. UpdatePerfInfo(&PerfListItem[ListIndex].UserTime[0],0,NULL);
  305. UpdatePerfInfo(&PerfListItem[ListIndex].TotalTime[0],100,NULL);
  306. UpdatePerfInfo(&PerfListItem[ListIndex].KernelTime[0],0,NULL);
  307. UpdatePerfInfo(&PerfListItem[ListIndex].DpcTime[0],0,NULL);
  308. UpdatePerfInfo(&PerfListItem[ListIndex].InterruptTime[0],0,NULL);
  309. }
  310. }
  311. //
  312. // save pagefaults and update next entry
  313. //
  314. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  315. &PerfListItem[ListIndex].TotalTime[0],
  316. PerfInfo.PageFaultCount - PreviousPerfInfo.PageFaultCount,
  317. &PerfListItem[ListIndex].Max);
  318. ListIndex++;
  319. //
  320. // save pages available
  321. //
  322. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  323. &PerfListItem[ListIndex].TotalTime[0],
  324. PerfInfo.AvailablePages,
  325. &PerfListItem[ListIndex].Max);
  326. ListIndex++;
  327. //
  328. // save context switch count per interval
  329. //
  330. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  331. &PerfListItem[ListIndex].TotalTime[0],
  332. (PerfInfo.ContextSwitches - PreviousPerfInfo.ContextSwitches)/DELAY_SECONDS,
  333. &PerfListItem[ListIndex].Max);
  334. ListIndex++;
  335. //
  336. // save first level TB fills per period
  337. //
  338. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  339. &PerfListItem[ListIndex].TotalTime[0],
  340. (PerfInfo.FirstLevelTbFills - PreviousPerfInfo.FirstLevelTbFills)/DELAY_SECONDS,
  341. &PerfListItem[ListIndex].Max);
  342. ListIndex++;
  343. //
  344. // save second level tb fills per period
  345. //
  346. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  347. &PerfListItem[ListIndex].TotalTime[0],
  348. (PerfInfo.SecondLevelTbFills - PreviousPerfInfo.SecondLevelTbFills)/DELAY_SECONDS,
  349. &PerfListItem[ListIndex].Max);
  350. ListIndex++;
  351. //
  352. // save system calls per time interval
  353. //
  354. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  355. &PerfListItem[ListIndex].TotalTime[0],
  356. (PerfInfo.SystemCalls - PreviousPerfInfo.SystemCalls)/DELAY_SECONDS,
  357. &PerfListItem[ListIndex].Max);
  358. ListIndex++;
  359. //
  360. // save interrupt count per interval
  361. //
  362. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  363. &PerfListItem[ListIndex].TotalTime[0],
  364. (InterruptCount - PreviousInterruptCount)/DELAY_SECONDS,
  365. &PerfListItem[ListIndex].Max);
  366. ListIndex++;
  367. //
  368. // save paged pool pages
  369. //
  370. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  371. &PerfListItem[ListIndex].TotalTime[0],
  372. PerfInfo.PagedPoolPages,
  373. &PerfListItem[ListIndex].Max);
  374. ListIndex++;
  375. //
  376. // save non-paged pool pages
  377. //
  378. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  379. &PerfListItem[ListIndex].TotalTime[0],
  380. PerfInfo.NonPagedPoolPages,
  381. &PerfListItem[ListIndex].Max);
  382. ListIndex++;
  383. //
  384. // save Process count
  385. //
  386. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  387. &PerfListItem[ListIndex].TotalTime[0],
  388. ProcessCount,
  389. &PerfListItem[ListIndex].Max);
  390. ListIndex++;
  391. //
  392. // save ThreadCount
  393. //
  394. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  395. &PerfListItem[ListIndex].TotalTime[0],
  396. ThreadCount,
  397. &PerfListItem[ListIndex].Max);
  398. ListIndex++;
  399. //
  400. // save alignment fixup count per period
  401. //
  402. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  403. &PerfListItem[ListIndex].TotalTime[0],
  404. (ExceptionInfo.AlignmentFixupCount -
  405. PreviousExceptionInfo.AlignmentFixupCount),
  406. &PerfListItem[ListIndex].Max);
  407. ListIndex++;
  408. //
  409. // save exception dispatch count per period
  410. //
  411. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  412. &PerfListItem[ListIndex].TotalTime[0],
  413. (ExceptionInfo.ExceptionDispatchCount -
  414. PreviousExceptionInfo.ExceptionDispatchCount),
  415. &PerfListItem[ListIndex].Max);
  416. ListIndex++;
  417. //
  418. // save floating emulation count per period
  419. //
  420. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  421. &PerfListItem[ListIndex].TotalTime[0],
  422. (ExceptionInfo.FloatingEmulationCount -
  423. PreviousExceptionInfo.FloatingEmulationCount),
  424. &PerfListItem[ListIndex].Max);
  425. ListIndex++;
  426. //
  427. // save byte/word emulation count per period
  428. //
  429. PerfListItem[ListIndex].ChangeScale = UpdatePerfInfo(
  430. &PerfListItem[ListIndex].TotalTime[0],
  431. (ExceptionInfo.ByteWordEmulationCount -
  432. PreviousExceptionInfo.ByteWordEmulationCount),
  433. &PerfListItem[ListIndex].Max);
  434. ListIndex++;
  435. //
  436. // If the elapsed system time is not zero, then compute the percentage
  437. // of time spent in user and kdrnel mode. Otherwise, default the time
  438. // to zero.
  439. //
  440. if (TotalElapsedTime.QuadPart != 0) {
  441. //
  442. // Calculate and save total CPU value
  443. //
  444. ElapsedTime.QuadPart = TotalUserTime.QuadPart * 100;
  445. PercentTime.QuadPart = ElapsedTime.QuadPart / TotalElapsedTime.QuadPart;
  446. UpdatePerfInfo(&PerfListItem[ListIndex].UserTime[0],PercentTime.LowPart,NULL);
  447. ElapsedTime.QuadPart = TotalKernelTime.QuadPart * 100;
  448. PercentTime.QuadPart = ElapsedTime.QuadPart / TotalElapsedTime.QuadPart;
  449. UpdatePerfInfo(&PerfListItem[ListIndex].KernelTime[0],PercentTime.LowPart,NULL);
  450. ElapsedTime.QuadPart = TotalIdleTime.QuadPart *100;
  451. PercentTime.QuadPart = ElapsedTime.QuadPart / TotalElapsedTime.QuadPart;
  452. //
  453. // Save Total Time
  454. //
  455. Total = 100 - PercentTime.LowPart;
  456. if (Total > 100) {
  457. Total = 100;
  458. }
  459. UpdatePerfInfo(&PerfListItem[ListIndex].TotalTime[0],Total,NULL);
  460. ElapsedTime.QuadPart = TotalDpcTime.QuadPart *100;
  461. PercentTime.QuadPart = ElapsedTime.QuadPart / TotalElapsedTime.QuadPart;
  462. UpdatePerfInfo(&PerfListItem[ListIndex].DpcTime[0],PercentTime.LowPart,NULL);
  463. ElapsedTime.QuadPart = TotalInterruptTime.QuadPart *100;
  464. PercentTime.QuadPart = ElapsedTime.QuadPart / TotalElapsedTime.QuadPart;
  465. UpdatePerfInfo(&PerfListItem[ListIndex].InterruptTime[0],PercentTime.LowPart,NULL);
  466. } else {
  467. //
  468. // Set percentage of user and kernel time to zero.
  469. //
  470. UpdatePerfInfo(&PerfListItem[ListIndex].UserTime[0],0,NULL);
  471. UpdatePerfInfo(&PerfListItem[ListIndex].KernelTime[0],0,NULL);
  472. UpdatePerfInfo(&PerfListItem[ListIndex].DpcTime[0],0,NULL);
  473. UpdatePerfInfo(&PerfListItem[ListIndex].InterruptTime[0],0,NULL);
  474. UpdatePerfInfo(&PerfListItem[ListIndex].TotalTime[0],100,NULL);
  475. }
  476. //
  477. // done with setting values, save settings and return
  478. //
  479. PreviousExceptionInfo = ExceptionInfo;
  480. PreviousPerfInfo = PerfInfo;
  481. PreviousInterruptCount = InterruptCount;
  482. return(TRUE);
  483. }
  484. BOOL
  485. UpdatePerfInfo(
  486. PULONG DataPointer,
  487. ULONG NewDataValue,
  488. PULONG OldMaxValue
  489. )
  490. /*++
  491. Routine Description:
  492. Shift array of DATA_LIST_LENGTH USORTS and add the new value to the
  493. start of list
  494. Arguments:
  495. DataPointer - Pointer to the start of a DATA_LIST_LENGTH array
  496. NewDataValue - Data element to be added
  497. OldMaxValue - Scale value
  498. Return Value:
  499. TRUE is MaxValue must be increased or decreased
  500. Revision History:
  501. 10-21-91 Initial code
  502. --*/
  503. {
  504. ULONG Index;
  505. ULONG ScanMax;
  506. //
  507. // Shift DataArray while keeping track of the max value
  508. //
  509. // Set temp max to 100 to init a minimum maximum
  510. //
  511. ScanMax = 100;
  512. for (Index=DATA_LIST_LENGTH-1;Index>=1;Index--) {
  513. DataPointer[Index] = DataPointer[Index-1];
  514. if (DataPointer[Index] > ScanMax) {
  515. ScanMax = DataPointer[Index];
  516. }
  517. }
  518. //
  519. // add and check first value
  520. //
  521. DataPointer[0] = NewDataValue;
  522. if (NewDataValue > ScanMax) {
  523. ScanMax = NewDataValue;
  524. }
  525. //
  526. // If OldMaxValue = NULL then do not do a max limit check
  527. //
  528. if (OldMaxValue == NULL) {
  529. return(FALSE);
  530. }
  531. //
  532. // If Max values changed then undate the new max
  533. // value and return TRUE.
  534. //
  535. if (ScanMax != *OldMaxValue) {
  536. *OldMaxValue = ScanMax;
  537. return(TRUE);
  538. }
  539. return(FALSE);
  540. }