- corrected typo in epsapi.h
[reactos.git] / rosapps / sysutils / ctm / ctm.c
1 /* Console Task Manager
2
3 ctm.c - main program file
4
5 Written by: Aleksey Bragin (aleksey@studiocerebral.com)
6
7 Most of the code dealing with getting system parameters is taken from
8 ReactOS Task Manager written by Brian Palmer (brianp@reactos.org)
9
10 History:
11 09 April 2003 - v0.1, fixed bugs, added features, ported to mingw
12 20 March 2003 - v0.03, works good under ReactOS, and allows process
13 killing
14 18 March 2003 - Initial version 0.01, doesn't work under RectOS
15
16 This program is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation; either version 2 of the License, or
19 (at your option) any later version.
20
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
25
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
29
30
31 //#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows //headers
32 #include <windows.h>
33 #include <stdlib.h>
34 #include <malloc.h>
35 #include <memory.h>
36 #include <tchar.h>
37 #include <process.h>
38 #include <stdio.h>
39
40 #include <ddk/ntddk.h>
41 #include <epsapi.h>
42
43 #include "ctm.h"
44
45 #define MAX_PROC 17
46 //#define TIMES
47
48 HANDLE hStdin;
49 HANDLE hStdout;
50
51 DWORD inConMode;
52 DWORD outConMode;
53
54 //PROCNTQSI NtQuerySystemInformation= NULL;
55
56 const int ProcPerScreen = 17; // 17 processess are displayed on one page
57 ULONG ProcessCountOld = 0;
58 ULONG ProcessCount = 0;
59
60 double dbIdleTime;
61 double dbKernelTime;
62 double dbSystemTime;
63 LARGE_INTEGER liOldIdleTime = {{0,0}};
64 double OldKernelTime = 0;
65 LARGE_INTEGER liOldSystemTime = {{0,0}};
66
67 PPERFDATA pPerfDataOld = NULL; // Older perf data (saved to establish delta values)
68 PPERFDATA pPerfData = NULL; // Most recent copy of perf data
69
70 int selection=0;
71 int scrolled=0; // offset from which process start showing
72
73 #define NEW_CONSOLE
74
75 void *PsaiMalloc(SIZE_T size) { return malloc(size); }
76 void *PsaiRealloc(void *ptr, SIZE_T size) { return realloc(ptr, size); }
77 void PsaiFree(void *ptr) { free(ptr); }
78
79 // Prototypes
80 unsigned int GetKeyPressed();
81
82 void GetInputOutputHandles()
83 {
84 #ifdef NEW_CONSOLE
85 HANDLE console = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
86 FILE_SHARE_READ | FILE_SHARE_WRITE,
87 0, CONSOLE_TEXTMODE_BUFFER, 0);
88
89 if (SetConsoleActiveScreenBuffer(console) == FALSE)
90 {
91 hStdin = GetStdHandle(STD_INPUT_HANDLE);
92 hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
93 }
94 else
95 {
96 hStdin = GetStdHandle(STD_INPUT_HANDLE);//console;
97 hStdout = console;
98 }
99 #else
100 hStdin = GetStdHandle(STD_INPUT_HANDLE);
101 hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
102 #endif
103 }
104
105 void RestoreConsole()
106 {
107 SetConsoleMode(hStdin, inConMode);
108 SetConsoleMode(hStdout, outConMode);
109
110 #ifdef NEW_CONSOLE
111 SetConsoleActiveScreenBuffer(GetStdHandle(STD_OUTPUT_HANDLE));
112 #endif
113 }
114
115 void DisplayScreen()
116 {
117 COORD pos;
118 char lpStr[80];
119 int idx;
120 DWORD numChars;
121 int lines;
122
123 // Header
124 pos.X = 2; pos.Y = 2;
125 strcpy(lpStr, "Console TaskManager v0.1 by Aleksey Bragin <aleksey@studiocerebral.com>");
126 WriteConsoleOutputCharacter(hStdout, lpStr, strlen(lpStr), pos, &numChars);
127
128 pos.X = 2; pos.Y = 3;
129 strcpy(lpStr, "+-------------------------------+-------+-----+-----------+-------------+");
130 WriteConsoleOutputCharacter(hStdout, lpStr, strlen(lpStr), pos, &numChars);
131
132 pos.X = 2; pos.Y = 4;
133 strcpy(lpStr, "| Image name | PID | CPU | Mem Usage | Page Faults |");
134 WriteConsoleOutputCharacter(hStdout, lpStr, strlen(lpStr), pos, &numChars);
135
136 pos.X = 2; pos.Y = 5;
137 strcpy(lpStr, "+-------------------------------+-------+-----+-----------+-------------+");
138 WriteConsoleOutputCharacter(hStdout, lpStr, strlen(lpStr), pos, &numChars);
139
140 // Footer
141 pos.X = 2; pos.Y = 23;
142 strcpy(lpStr, "+-------------------------------+-------+-----+-----------+-------------+");
143 WriteConsoleOutputCharacter(hStdout, lpStr, strlen(lpStr), pos, &numChars);
144
145 // Menu
146 pos.X = 2; pos.Y = 24;
147 strcpy(lpStr, "Press: q - quit, k - kill process ");
148 WriteConsoleOutputCharacter(hStdout, lpStr, strlen(lpStr), pos, &numChars);
149
150 // Processess
151 lines = ProcessCount;
152 if (lines > MAX_PROC)
153 lines = MAX_PROC;
154 for (idx=0; idx<lines; idx++)
155 {
156 int len;
157 char imgName[MAX_PATH];
158 char lpPid[8];
159 char lpCpu[6];
160 char lpMemUsg[12];
161 char lpPageFaults[15];
162 WORD wColor;
163
164 // data
165 // image name
166 pos.X = 3; pos.Y = 6+idx;
167 memset(imgName, 0, MAX_PATH);
168 WideCharToMultiByte(CP_ACP, 0, pPerfData[scrolled+idx].ImageName, -1,
169 imgName, MAX_PATH, NULL, NULL);
170 len = strlen(imgName);
171 WriteConsoleOutputCharacter(hStdout, " ", 30, pos, &numChars);
172 WriteConsoleOutputCharacter(hStdout, imgName, (len > 30) ? 30 : len, pos, &numChars);
173
174 // PID
175 pos.X = 35; pos.Y = 6+idx;
176 sprintf(lpPid, "%6ld", pPerfData[scrolled+idx].ProcessId);
177 WriteConsoleOutputCharacter(hStdout, lpPid, strlen(lpPid), pos, &numChars);
178
179 // CPU
180 pos.X = 43; pos.Y = 6+idx;
181 sprintf(lpCpu, "%3d%%", pPerfData[scrolled+idx].CPUUsage);
182 WriteConsoleOutputCharacter(hStdout, lpCpu, strlen(lpCpu), pos, &numChars);
183
184 // Mem usage
185 pos.X = 49; pos.Y = 6+idx;
186 sprintf(lpMemUsg, "%6ld", pPerfData[scrolled+idx].WorkingSetSizeBytes / 1024);
187 WriteConsoleOutputCharacter(hStdout, lpMemUsg, strlen(lpMemUsg), pos, &numChars);
188
189 // Page Fault
190 pos.X = 61; pos.Y = 6+idx;
191 sprintf(lpPageFaults, "%12ld", pPerfData[scrolled+idx].PageFaultCount);
192 WriteConsoleOutputCharacter(hStdout, lpPageFaults, strlen(lpPageFaults), pos, &numChars);
193
194 // columns
195 pos.X = 2; pos.Y = 6+idx;
196 WriteConsoleOutputCharacter(hStdout, "|", 1, pos, &numChars);
197 pos.X = 34; pos.Y = 6+idx;
198 WriteConsoleOutputCharacter(hStdout, "|", 1, pos, &numChars);
199 pos.X = 42; pos.Y = 6+idx;
200 WriteConsoleOutputCharacter(hStdout, "|", 1, pos, &numChars);
201 pos.X = 48; pos.Y = 6+idx;
202 WriteConsoleOutputCharacter(hStdout, "|", 1, pos, &numChars);
203 pos.X = 60; pos.Y = 6+idx;
204 WriteConsoleOutputCharacter(hStdout, "|", 1, pos, &numChars);
205 pos.X = 74; pos.Y = 6+idx;
206 WriteConsoleOutputCharacter(hStdout, "|", 1, pos, &numChars);
207
208
209 // Attributes now...
210 pos.X = 3; pos.Y = 6+idx;
211 if (selection == idx)
212 {
213 wColor = BACKGROUND_GREEN |
214 FOREGROUND_RED |
215 FOREGROUND_GREEN |
216 FOREGROUND_BLUE;
217 }
218 else
219 {
220 wColor = BACKGROUND_BLUE |
221 FOREGROUND_RED |
222 FOREGROUND_GREEN |
223 FOREGROUND_BLUE;
224 }
225
226 FillConsoleOutputAttribute(
227 hStdout, // screen buffer handle
228 wColor, // color to fill with
229 31, // number of cells to fill
230 pos, // first cell to write to
231 &numChars); // actual number written
232 }
233
234 return;
235 }
236
237 // returns TRUE if exiting
238 int ProcessKeys(int numEvents)
239 {
240 if ((ProcessCount-scrolled < 17) && (ProcessCount > 17))
241 scrolled = ProcessCount-17;
242
243 unsigned char key = GetKeyPressed(numEvents);
244 if (key == VK_Q)
245 return TRUE;
246 else if (key == VK_K)
247 {
248 // user wants to kill some process, get his acknowledgement
249 DWORD pId;
250 COORD pos;
251 char lpStr[100];
252
253 pos.X = 2; pos.Y = 24;
254 strcpy(lpStr, "Are you sure you want to kill this process? (y/n)");
255 WriteConsoleOutputCharacter(hStdout, lpStr, strlen(lpStr), pos, &pId);
256
257 do {
258 GetNumberOfConsoleInputEvents(hStdin, &pId);
259 key = GetKeyPressed(pId);
260 } while (key == 0);
261
262 if (key == VK_Y)
263 {
264 HANDLE hProcess;
265 pId = pPerfData[selection+scrolled].ProcessId;
266 hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pId);
267
268 if (hProcess)
269 {
270 if (!TerminateProcess(hProcess, 0))
271 {
272 strcpy(lpStr, "Unable to terminate this process... ");
273 WriteConsoleOutputCharacter(hStdout, lpStr, strlen(lpStr), pos, &pId);
274 Sleep(1000);
275 }
276
277 CloseHandle(hProcess);
278 }
279 else
280 {
281 sprintf(lpStr, "Unable to terminate process %3d (unable to OpenProcess) ", pId);
282 WriteConsoleOutputCharacter(hStdout, lpStr, strlen(lpStr), pos, &pId);
283 Sleep(1000);
284 }
285 }
286 }
287 else if (key == VK_UP)
288 {
289 if (selection > 0)
290 selection--;
291 else if ((selection == 0) && (scrolled > 0))
292 scrolled--;
293 }
294 else if (key == VK_DOWN)
295 {
296 if ((selection < MAX_PROC-1) && (selection < ProcessCount-1))
297 selection++;
298 else if ((selection == MAX_PROC-1) && (selection+scrolled < ProcessCount-1))
299 scrolled++;
300 }
301
302 return FALSE;
303 }
304
305 void PerfInit()
306 {
307 // NtQuerySystemInformation = //(PROCNTQSI)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), //"NtQuerySystemInformation");
308 }
309
310 void PerfDataRefresh()
311 {
312 LONG status;
313 ULONG ulSize;
314 LPBYTE pBuffer;
315 ULONG BufferSize;
316 ULONG Idx, Idx2;
317 HANDLE hProcess;
318 HANDLE hProcessToken;
319 PSYSTEM_PROCESSES pSPI;
320 PPERFDATA pPDOld;
321 TCHAR szTemp[MAX_PATH];
322 DWORD dwSize;
323 double CurrentKernelTime;
324 PSYSTEM_PROCESSORTIME_INFO SysProcessorTimeInfo;
325 SYSTEM_PERFORMANCE_INFO SysPerfInfo;
326 SYSTEM_TIMEOFDAY_INFORMATION SysTimeInfo;
327
328 #ifdef TIMES
329 // Get new system time
330 status = NtQuerySystemInformation(SystemTimeInformation, &SysTimeInfo, sizeof(SysTimeInfo), 0);
331 if (status != NO_ERROR)
332 return;
333
334 // Get new CPU's idle time
335 status = NtQuerySystemInformation(SystemPerformanceInformation, &SysPerfInfo, sizeof(SysPerfInfo), NULL);
336 if (status != NO_ERROR)
337 return;
338 #endif
339 // Get processor information
340 SysProcessorTimeInfo = (PSYSTEM_PROCESSORTIME_INFO)malloc(sizeof(SYSTEM_PROCESSORTIME_INFO) * 1/*SystemBasicInfo.bKeNumberProcessors*/);
341 status = NtQuerySystemInformation(SystemProcessorTimes, SysProcessorTimeInfo, sizeof(SYSTEM_PROCESSORTIME_INFO) * 1/*SystemBasicInfo.bKeNumberProcessors*/, &ulSize);
342
343
344 // Get process information
345 PsaCaptureProcessesAndThreads((PSYSTEM_PROCESSES *)&pBuffer);
346
347 #ifdef TIMES
348 for (CurrentKernelTime=0, Idx=0; Idx<1/*SystemBasicInfo.bKeNumberProcessors*/; Idx++) {
349 CurrentKernelTime += Li2Double(SysProcessorTimeInfo[Idx].KernelTime);
350 CurrentKernelTime += Li2Double(SysProcessorTimeInfo[Idx].DpcTime);
351 CurrentKernelTime += Li2Double(SysProcessorTimeInfo[Idx].InterruptTime);
352 }
353
354 // If it's a first call - skip idle time calcs
355 if (liOldIdleTime.QuadPart != 0) {
356 // CurrentValue = NewValue - OldValue
357 dbIdleTime = Li2Double(SysPerfInfo.liIdleTime) - Li2Double(liOldIdleTime);
358 dbKernelTime = CurrentKernelTime - OldKernelTime;
359 dbSystemTime = Li2Double(SysTimeInfo.CurrentTime) - Li2Double(liOldSystemTime);
360
361 // CurrentCpuIdle = IdleTime / SystemTime
362 dbIdleTime = dbIdleTime / dbSystemTime;
363 dbKernelTime = dbKernelTime / dbSystemTime;
364
365 // CurrentCpuUsage% = 100 - (CurrentCpuIdle * 100) / NumberOfProcessors
366 dbIdleTime = 100.0 - dbIdleTime * 100.0; /* / (double)SystemBasicInfo.bKeNumberProcessors;// + 0.5; */
367 dbKernelTime = 100.0 - dbKernelTime * 100.0; /* / (double)SystemBasicInfo.bKeNumberProcessors;// + 0.5; */
368 }
369
370 // Store new CPU's idle and system time
371 liOldIdleTime = SysPerfInfo.liIdleTime;
372 liOldSystemTime = SysTimeInfo.CurrentTime;
373 OldKernelTime = CurrentKernelTime;
374 #endif
375
376 // Determine the process count
377 // We loop through the data we got from PsaCaptureProcessesAndThreads
378 // and count how many structures there are (until PsaWalkNextProcess
379 // returns NULL)
380 ProcessCountOld = ProcessCount;
381 ProcessCount = 0;
382 pSPI = PsaWalkFirstProcess((PSYSTEM_PROCESSES)pBuffer);
383 while (pSPI) {
384 ProcessCount++;
385 pSPI = PsaWalkNextProcess(pSPI);
386 }
387
388 // Now alloc a new PERFDATA array and fill in the data
389 if (pPerfDataOld) {
390 //delete[] pPerfDataOld;
391 free(pPerfDataOld);
392 }
393 pPerfDataOld = pPerfData;
394 //pPerfData = new PERFDATA[ProcessCount];
395 pPerfData = (PPERFDATA)malloc(sizeof(PERFDATA) * ProcessCount);
396 pSPI = PsaWalkFirstProcess((PSYSTEM_PROCESSES)pBuffer);
397 for (Idx=0; Idx<ProcessCount; Idx++) {
398 // Get the old perf data for this process (if any)
399 // so that we can establish delta values
400 pPDOld = NULL;
401 for (Idx2=0; Idx2<ProcessCountOld; Idx2++) {
402 if (pPerfDataOld[Idx2].ProcessId == pSPI->ProcessId) {
403 pPDOld = &pPerfDataOld[Idx2];
404 break;
405 }
406 }
407
408 // Clear out process perf data structure
409 memset(&pPerfData[Idx], 0, sizeof(PERFDATA));
410
411 if (pSPI->ProcessName.Buffer)
412 wcsncpy(pPerfData[Idx].ImageName, pSPI->ProcessName.Buffer, pSPI->ProcessName.MaximumLength);
413 else
414 wcscpy(pPerfData[Idx].ImageName, L"System Idle Process");
415
416 pPerfData[Idx].ProcessId = pSPI->ProcessId;
417
418 if (pPDOld) {
419 #ifdef TIMES
420 double CurTime = Li2Double(pSPI->KernelTime) + Li2Double(pSPI->UserTime);
421 double OldTime = Li2Double(pPDOld->KernelTime) + Li2Double(pPDOld->UserTime);
422 double CpuTime = (CurTime - OldTime) / dbSystemTime;
423 CpuTime = CpuTime * 100.0; /* / (double)SystemBasicInfo.bKeNumberProcessors;// + 0.5;*/
424
425 pPerfData[Idx].CPUUsage = (ULONG)CpuTime;
426 #else
427 pPerfData[Idx].CPUUsage = 0;
428 #endif
429 }
430
431 pPerfData[Idx].CPUTime.QuadPart = pSPI->UserTime.QuadPart + pSPI->KernelTime.QuadPart;
432 pPerfData[Idx].WorkingSetSizeBytes = pSPI->VmCounters.WorkingSetSize;
433 pPerfData[Idx].PeakWorkingSetSizeBytes = pSPI->VmCounters.PeakWorkingSetSize;
434 if (pPDOld)
435 pPerfData[Idx].WorkingSetSizeDelta = labs((LONG)pSPI->VmCounters.WorkingSetSize - (LONG)pPDOld->WorkingSetSizeBytes);
436 else
437 pPerfData[Idx].WorkingSetSizeDelta = 0;
438 pPerfData[Idx].PageFaultCount = pSPI->VmCounters.PageFaultCount;
439 if (pPDOld)
440 pPerfData[Idx].PageFaultCountDelta = labs((LONG)pSPI->VmCounters.PageFaultCount - (LONG)pPDOld->PageFaultCount);
441 else
442 pPerfData[Idx].PageFaultCountDelta = 0;
443 pPerfData[Idx].VirtualMemorySizeBytes = pSPI->VmCounters.VirtualSize;
444 pPerfData[Idx].PagedPoolUsagePages = pSPI->VmCounters.QuotaPagedPoolUsage;
445 pPerfData[Idx].NonPagedPoolUsagePages = pSPI->VmCounters.QuotaNonPagedPoolUsage;
446 pPerfData[Idx].BasePriority = pSPI->BasePriority;
447 pPerfData[Idx].HandleCount = pSPI->HandleCount;
448 pPerfData[Idx].ThreadCount = pSPI->ThreadCount;
449 //pPerfData[Idx].SessionId = pSPI->SessionId;
450
451 #ifdef EXTRA_INFO
452 hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pSPI->ProcessId);
453 if (hProcess) {
454 if (OpenProcessToken(hProcess, TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_IMPERSONATE, &hProcessToken)) {
455 ImpersonateLoggedOnUser(hProcessToken);
456 memset(szTemp, 0, sizeof(TCHAR[MAX_PATH]));
457 dwSize = MAX_PATH;
458 GetUserName(szTemp, &dwSize);
459 #ifndef UNICODE
460 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szTemp, -1, pPerfData[Idx].UserName, MAX_PATH);
461 /*
462 int MultiByteToWideChar(
463 UINT CodePage, // code page
464 DWORD dwFlags, // character-type options
465 LPCSTR lpMultiByteStr, // string to map
466 int cbMultiByte, // number of bytes in string
467 LPWSTR lpWideCharStr, // wide-character buffer
468 int cchWideChar // size of buffer
469 );
470 */
471 #endif
472 RevertToSelf();
473 CloseHandle(hProcessToken);
474 }
475 CloseHandle(hProcess);
476 }
477 #endif
478 #ifdef TIMES
479 pPerfData[Idx].UserTime.QuadPart = pSPI->UserTime.QuadPart;
480 pPerfData[Idx].KernelTime.QuadPart = pSPI->KernelTime.QuadPart;
481 #endif
482 pSPI = PsaWalkNextProcess(pSPI);
483 }
484 //delete[] pBuffer;
485 PsaFreeCapture(pBuffer);
486
487 free(SysProcessorTimeInfo);
488 }
489
490 // Code partly taken from slw32tty.c from mc/slang
491 unsigned int GetKeyPressed(int events)
492 {
493 long key;
494 DWORD bytesRead;
495 INPUT_RECORD record;
496 int i;
497
498
499 for (i=0; i<events; i++)
500 {
501 if (!ReadConsoleInput(hStdin, &record, 1, &bytesRead)) {
502 return 0;
503 }
504
505 if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown)
506 return record.Event.KeyEvent.wVirtualKeyCode;//.uChar.AsciiChar;
507 }
508
509 return 0;
510 }
511
512
513 int main(int *argc, char **argv)
514 {
515 GetInputOutputHandles();
516
517 if (hStdin == INVALID_HANDLE_VALUE || hStdout == INVALID_HANDLE_VALUE)
518 {
519 printf("ctm: can't use console.");
520 return -1;
521 }
522
523 if (GetConsoleMode(hStdin, &inConMode) == 0)
524 {
525 printf("ctm: can't GetConsoleMode() for input console.");
526 return -1;
527 }
528
529 if (GetConsoleMode(hStdout, &outConMode) == 0)
530 {
531 printf("ctm: can't GetConsoleMode() for output console.");
532 return -1;
533 }
534
535 SetConsoleMode(hStdin, 0); //FIXME: Should check for error!
536 SetConsoleMode(hStdout, 0); //FIXME: Should check for error!
537
538 PerfInit();
539
540 while (1)
541 {
542 DWORD numEvents;
543
544 PerfDataRefresh();
545 DisplayScreen();
546
547 //WriteConsole(hStdin, " ", 1, &numEvents, NULL); // TODO: Make another way (this is ugly, I know)
548 GetNumberOfConsoleInputEvents(hStdin, &numEvents);
549
550 if (numEvents > 0)
551 {
552 if (ProcessKeys(numEvents) == TRUE)
553 break;
554 }
555 else
556 Sleep(40); // TODO: Should be done more efficient (might be another thread handling input/etc)*/
557 }
558
559 RestoreConsole();
560 return 0;
561 }