/* * Process Hacker ToolStatus - * Toolbar Graph Bands * * Copyright (C) 2015-2016 dmex * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ #include "toolstatus.h" HWND CpuGraphHandle = NULL; HWND MemGraphHandle = NULL; HWND CommitGraphHandle = NULL; HWND IoGraphHandle = NULL; static PH_GRAPH_STATE CpuGraphState; static PH_GRAPH_STATE MemGraphState; static PH_GRAPH_STATE CommitGraphState; static PH_GRAPH_STATE IoGraphState; VOID ToolbarCreateGraphs(VOID) { UINT height = (UINT)SendMessage(RebarHandle, RB_GETROWHEIGHT, 0, 0); if (ToolStatusConfig.CpuGraphEnabled && !CpuGraphHandle) { CpuGraphHandle = CreateWindow( PH_GRAPH_CLASSNAME, NULL, WS_VISIBLE | WS_CHILD | WS_BORDER, 0, 0, 0, 0, PhMainWndHandle, NULL, NULL, NULL ); Graph_SetTooltip(CpuGraphHandle, TRUE); PhInitializeGraphState(&CpuGraphState); } if (ToolStatusConfig.MemGraphEnabled && !MemGraphHandle) { MemGraphHandle = CreateWindow( PH_GRAPH_CLASSNAME, NULL, WS_VISIBLE | WS_CHILD | WS_BORDER, 0, 0, 0, 0, PhMainWndHandle, NULL, NULL, NULL ); Graph_SetTooltip(MemGraphHandle, TRUE); PhInitializeGraphState(&MemGraphState); } if (ToolStatusConfig.CommitGraphEnabled && !CommitGraphHandle) { CommitGraphHandle = CreateWindow( PH_GRAPH_CLASSNAME, NULL, WS_VISIBLE | WS_CHILD | WS_BORDER, 0, 0, 0, 0, PhMainWndHandle, NULL, NULL, NULL ); Graph_SetTooltip(CommitGraphHandle, TRUE); PhInitializeGraphState(&CommitGraphState); } if (ToolStatusConfig.IoGraphEnabled && !IoGraphHandle) { IoGraphHandle = CreateWindow( PH_GRAPH_CLASSNAME, NULL, WS_VISIBLE | WS_CHILD | WS_BORDER, 0, 0, 0, 0, PhMainWndHandle, NULL, NULL, NULL ); Graph_SetTooltip(IoGraphHandle, TRUE); PhInitializeGraphState(&IoGraphState); } if (ToolStatusConfig.CpuGraphEnabled) { if (!RebarBandExists(REBAR_BAND_ID_CPUGRAPH)) RebarBandInsert(REBAR_BAND_ID_CPUGRAPH, CpuGraphHandle, 145, height); // 85 if (CpuGraphHandle && !IsWindowVisible(CpuGraphHandle)) ShowWindow(CpuGraphHandle, SW_SHOW); } else { if (RebarBandExists(REBAR_BAND_ID_CPUGRAPH)) RebarBandRemove(REBAR_BAND_ID_CPUGRAPH); if (CpuGraphHandle) { PhDeleteGraphState(&CpuGraphState); DestroyWindow(CpuGraphHandle); CpuGraphHandle = NULL; } } if (ToolStatusConfig.MemGraphEnabled) { if (!RebarBandExists(REBAR_BAND_ID_MEMGRAPH)) RebarBandInsert(REBAR_BAND_ID_MEMGRAPH, MemGraphHandle, 145, height); // 85 if (MemGraphHandle && !IsWindowVisible(MemGraphHandle)) { ShowWindow(MemGraphHandle, SW_SHOW); } } else { if (RebarBandExists(REBAR_BAND_ID_MEMGRAPH)) RebarBandRemove(REBAR_BAND_ID_MEMGRAPH); if (MemGraphHandle) { PhDeleteGraphState(&MemGraphState); DestroyWindow(MemGraphHandle); MemGraphHandle = NULL; } } if (ToolStatusConfig.CommitGraphEnabled) { if (!RebarBandExists(REBAR_BAND_ID_COMMITGRAPH)) RebarBandInsert(REBAR_BAND_ID_COMMITGRAPH, CommitGraphHandle, 145, height); // 85 if (CommitGraphHandle && !IsWindowVisible(CommitGraphHandle)) { ShowWindow(CommitGraphHandle, SW_SHOW); } } else { if (RebarBandExists(REBAR_BAND_ID_COMMITGRAPH)) RebarBandRemove(REBAR_BAND_ID_COMMITGRAPH); if (CommitGraphHandle) { PhDeleteGraphState(&CommitGraphState); DestroyWindow(CommitGraphHandle); CommitGraphHandle = NULL; } } if (ToolStatusConfig.IoGraphEnabled) { if (!RebarBandExists(REBAR_BAND_ID_IOGRAPH)) RebarBandInsert(REBAR_BAND_ID_IOGRAPH, IoGraphHandle, 145, height); // 85 if (IoGraphHandle && !IsWindowVisible(IoGraphHandle)) { ShowWindow(IoGraphHandle, SW_SHOW); } } else { if (RebarBandExists(REBAR_BAND_ID_IOGRAPH)) RebarBandRemove(REBAR_BAND_ID_IOGRAPH); if (IoGraphHandle) { PhDeleteGraphState(&IoGraphState); DestroyWindow(IoGraphHandle); IoGraphHandle = NULL; } } } VOID ToolbarUpdateGraphs(VOID) { if (ToolStatusConfig.CpuGraphEnabled && CpuGraphHandle) { CpuGraphState.Valid = FALSE; CpuGraphState.TooltipIndex = -1; Graph_MoveGrid(CpuGraphHandle, 1); Graph_Draw(CpuGraphHandle); Graph_UpdateTooltip(CpuGraphHandle); InvalidateRect(CpuGraphHandle, NULL, FALSE); } if (ToolStatusConfig.MemGraphEnabled && MemGraphHandle) { MemGraphState.Valid = FALSE; MemGraphState.TooltipIndex = -1; Graph_MoveGrid(MemGraphHandle, 1); Graph_Draw(MemGraphHandle); Graph_UpdateTooltip(MemGraphHandle); InvalidateRect(MemGraphHandle, NULL, FALSE); } if (ToolStatusConfig.CommitGraphEnabled && CommitGraphHandle) { CommitGraphState.Valid = FALSE; CommitGraphState.TooltipIndex = -1; Graph_MoveGrid(CommitGraphHandle, 1); Graph_Draw(CommitGraphHandle); Graph_UpdateTooltip(CommitGraphHandle); InvalidateRect(CommitGraphHandle, NULL, FALSE); } if (ToolStatusConfig.IoGraphEnabled && IoGraphHandle) { IoGraphState.Valid = FALSE; IoGraphState.TooltipIndex = -1; Graph_MoveGrid(IoGraphHandle, 1); Graph_Draw(IoGraphHandle); Graph_UpdateTooltip(IoGraphHandle); InvalidateRect(IoGraphHandle, NULL, FALSE); } } // // BEGIN copied from ProcessHacker/sysinfo.c // static PPH_PROCESS_RECORD PhSipReferenceMaxCpuRecord( _In_ LONG Index ) { LARGE_INTEGER time; LONG maxProcessIdLong; HANDLE maxProcessId; // Find the process record for the max. CPU process for the particular time. maxProcessIdLong = PhGetItemCircularBuffer_ULONG(SystemStatistics.MaxCpuHistory, Index); if (!maxProcessIdLong) return NULL; // This must be treated as a signed integer to handle Interrupts correctly. maxProcessId = LongToHandle(maxProcessIdLong); // Note that the time we get has its components beyond seconds cleared. // For example: // * At 2.5 seconds a process is started. // * At 2.75 seconds our process provider is fired, and the process is determined // to have 75% CPU usage, which happens to be the maximum CPU usage. // * However the 2.75 seconds is recorded as 2 seconds due to // RtlTimeToSecondsSince1980. // * If we call PhFindProcessRecord, it cannot find the process because it was // started at 2.5 seconds, not 2 seconds or older. // // This mean we must add one second minus one tick (100ns) to the time, giving us // 2.9999999 seconds. This will then make sure we find the process. PhGetStatisticsTime(NULL, Index, &time); time.QuadPart += PH_TICKS_PER_SEC - 1; return PhFindProcessRecord(maxProcessId, &time); } static PPH_STRING PhSipGetMaxCpuString( _In_ LONG Index ) { PPH_PROCESS_RECORD maxProcessRecord; FLOAT maxCpuUsage; PPH_STRING maxUsageString = NULL; if (maxProcessRecord = PhSipReferenceMaxCpuRecord(Index)) { // We found the process record, so now we construct the max. usage string. maxCpuUsage = PhGetItemCircularBuffer_FLOAT(SystemStatistics.MaxCpuUsageHistory, Index); // Make sure we don't try to display the PID of DPCs or Interrupts. if (!PH_IS_FAKE_PROCESS_ID(maxProcessRecord->ProcessId)) { maxUsageString = PhaFormatString( L"\n%s (%u): %.2f%%", maxProcessRecord->ProcessName->Buffer, HandleToUlong(maxProcessRecord->ProcessId), maxCpuUsage * 100 ); } else { maxUsageString = PhaFormatString( L"\n%s: %.2f%%", maxProcessRecord->ProcessName->Buffer, maxCpuUsage * 100 ); } PhDereferenceProcessRecord(maxProcessRecord); } return maxUsageString; } static PPH_PROCESS_RECORD PhSipReferenceMaxIoRecord( _In_ LONG Index ) { LARGE_INTEGER time; ULONG maxProcessId; // Find the process record for the max. I/O process for the particular time. maxProcessId = PhGetItemCircularBuffer_ULONG(SystemStatistics.MaxIoHistory, Index); if (!maxProcessId) return NULL; // See above for the explanation. PhGetStatisticsTime(NULL, Index, &time); time.QuadPart += PH_TICKS_PER_SEC - 1; return PhFindProcessRecord(UlongToHandle(maxProcessId), &time); } static PPH_STRING PhSipGetMaxIoString( _In_ LONG Index ) { PPH_PROCESS_RECORD maxProcessRecord; ULONG64 maxIoReadOther; ULONG64 maxIoWrite; PPH_STRING maxUsageString = NULL; if (maxProcessRecord = PhSipReferenceMaxIoRecord(Index)) { // We found the process record, so now we construct the max. usage string. maxIoReadOther = PhGetItemCircularBuffer_ULONG64(SystemStatistics.MaxIoReadOtherHistory, Index); maxIoWrite = PhGetItemCircularBuffer_ULONG64(SystemStatistics.MaxIoWriteHistory, Index); if (!PH_IS_FAKE_PROCESS_ID(maxProcessRecord->ProcessId)) { maxUsageString = PhaFormatString( L"\n%s (%u): R+O: %s, W: %s", maxProcessRecord->ProcessName->Buffer, HandleToUlong(maxProcessRecord->ProcessId), PhaFormatSize(maxIoReadOther, -1)->Buffer, PhaFormatSize(maxIoWrite, -1)->Buffer ); } else { maxUsageString = PhaFormatString( L"\n%s: R+O: %s, W: %s", maxProcessRecord->ProcessName->Buffer, PhaFormatSize(maxIoReadOther, -1)->Buffer, PhaFormatSize(maxIoWrite, -1)->Buffer ); } PhDereferenceProcessRecord(maxProcessRecord); } return maxUsageString; } // // END copied from ProcessHacker/sysinfo.c // VOID ToolbarUpdateGraphsInfo(LPNMHDR Header) { switch (Header->code) { case GCN_GETDRAWINFO: { if (ToolStatusConfig.CpuGraphEnabled && Header->hwndFrom == CpuGraphHandle) { PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_LINE_2; PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), PhGetIntegerSetting(L"ColorCpuUser")); if (ProcessesUpdatedCount < 2) return; PhGraphStateGetDrawInfo(&CpuGraphState, getDrawInfo, SystemStatistics.CpuUserHistory->Count); if (!CpuGraphState.Valid) { PhCopyCircularBuffer_FLOAT(SystemStatistics.CpuKernelHistory, CpuGraphState.Data1, drawInfo->LineDataCount); PhCopyCircularBuffer_FLOAT(SystemStatistics.CpuUserHistory, CpuGraphState.Data2, drawInfo->LineDataCount); CpuGraphState.Valid = TRUE; } } else if (ToolStatusConfig.MemGraphEnabled && Header->hwndFrom == MemGraphHandle) { PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; drawInfo->Flags = PH_GRAPH_USE_GRID_X; PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorPhysical"), 0); if (ProcessesUpdatedCount < 2) return; PhGraphStateGetDrawInfo(&MemGraphState, getDrawInfo, SystemStatistics.PhysicalHistory->Count); if (!MemGraphState.Valid) { for (ULONG i = 0; i < drawInfo->LineDataCount; i++) { MemGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(SystemStatistics.PhysicalHistory, i); } PhDivideSinglesBySingle( MemGraphState.Data1, (FLOAT)PhSystemBasicInformation.NumberOfPhysicalPages, drawInfo->LineDataCount ); MemGraphState.Valid = TRUE; } } else if (ToolStatusConfig.CommitGraphEnabled && Header->hwndFrom == CommitGraphHandle) { PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; drawInfo->Flags = PH_GRAPH_USE_GRID_X; PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorPrivate"), 0); if (ProcessesUpdatedCount < 2) return; PhGraphStateGetDrawInfo(&CommitGraphState, getDrawInfo, SystemStatistics.CommitHistory->Count); if (!CommitGraphState.Valid) { for (ULONG i = 0; i < drawInfo->LineDataCount; i++) { CommitGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(SystemStatistics.CommitHistory, i); } PhDivideSinglesBySingle( CommitGraphState.Data1, (FLOAT)SystemStatistics.Performance->CommitLimit, drawInfo->LineDataCount ); CommitGraphState.Valid = TRUE; } } else if (ToolStatusConfig.IoGraphEnabled && Header->hwndFrom == IoGraphHandle) { PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_LINE_2; PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); if (ProcessesUpdatedCount < 2) return; PhGraphStateGetDrawInfo(&IoGraphState, getDrawInfo, SystemStatistics.IoReadHistory->Count); if (!IoGraphState.Valid) { FLOAT max = 1024 * 1024; // minimum scaling of 1 MB. for (ULONG i = 0; i < drawInfo->LineDataCount; i++) { IoGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoReadHistory, i) + (FLOAT)PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoOtherHistory, i); IoGraphState.Data2[i] = (FLOAT)PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoWriteHistory, i); if (max < IoGraphState.Data1[i] + IoGraphState.Data2[i]) max = IoGraphState.Data1[i] + IoGraphState.Data2[i]; } PhDivideSinglesBySingle(IoGraphState.Data1, max, drawInfo->LineDataCount); PhDivideSinglesBySingle(IoGraphState.Data2, max, drawInfo->LineDataCount); IoGraphState.Valid = TRUE; } } } break; case GCN_GETTOOLTIPTEXT: { PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; if (getTooltipText->Index < getTooltipText->TotalCount) { if (ToolStatusConfig.CpuGraphEnabled && Header->hwndFrom == CpuGraphHandle) { if (CpuGraphState.TooltipIndex != getTooltipText->Index) { FLOAT cpuKernel; FLOAT cpuUser; cpuKernel = PhGetItemCircularBuffer_FLOAT(SystemStatistics.CpuKernelHistory, getTooltipText->Index); cpuUser = PhGetItemCircularBuffer_FLOAT(SystemStatistics.CpuUserHistory, getTooltipText->Index); PhMoveReference(&CpuGraphState.TooltipText, PhFormatString( L"%.2f%%%s\n%s", (cpuKernel + cpuUser) * 100, PhGetStringOrEmpty(PhSipGetMaxCpuString(getTooltipText->Index)), PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer )); } getTooltipText->Text = CpuGraphState.TooltipText->sr; } else if (ToolStatusConfig.MemGraphEnabled && Header->hwndFrom == MemGraphHandle) { ULONG physicalUsage; physicalUsage = PhGetItemCircularBuffer_ULONG(SystemStatistics.PhysicalHistory, getTooltipText->Index); PhMoveReference(&MemGraphState.TooltipText, PhFormatString( L"Physical memory: %s\n%s", PhaFormatSize(UInt32x32To64(physicalUsage, PAGE_SIZE), -1)->Buffer, PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer )); getTooltipText->Text = MemGraphState.TooltipText->sr; } else if (ToolStatusConfig.CommitGraphEnabled && Header->hwndFrom == CommitGraphHandle) { ULONG commitUsage; commitUsage = PhGetItemCircularBuffer_ULONG(SystemStatistics.CommitHistory, getTooltipText->Index); PhMoveReference(&CommitGraphState.TooltipText, PhFormatString( L"Commit charge: %s\n%s", PhaFormatSize(UInt32x32To64(commitUsage, PAGE_SIZE), -1)->Buffer, PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer )); getTooltipText->Text = CommitGraphState.TooltipText->sr; } else if (ToolStatusConfig.IoGraphEnabled && Header->hwndFrom == IoGraphHandle) { ULONG64 ioRead; ULONG64 ioWrite; ULONG64 ioOther; ioRead = PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoReadHistory, getTooltipText->Index); ioWrite = PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoWriteHistory, getTooltipText->Index); ioOther = PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoOtherHistory, getTooltipText->Index); PhMoveReference(&IoGraphState.TooltipText, PhFormatString( L"R: %s\nW: %s\nO: %s%s\n%s", PhaFormatSize(ioRead, -1)->Buffer, PhaFormatSize(ioWrite, -1)->Buffer, PhaFormatSize(ioOther, -1)->Buffer, PhGetStringOrEmpty(PhSipGetMaxIoString(getTooltipText->Index)), PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer )); getTooltipText->Text = IoGraphState.TooltipText->sr; } } } break; case GCN_MOUSEEVENT: { PPH_GRAPH_MOUSEEVENT mouseEvent = (PPH_GRAPH_MOUSEEVENT)Header; PPH_PROCESS_RECORD record = NULL; if (ToolStatusConfig.CpuGraphEnabled && Header->hwndFrom == CpuGraphHandle) { if (mouseEvent->Message == WM_RBUTTONUP) { ShowCustomizeMenu(); } else { if (mouseEvent->Message == WM_LBUTTONDBLCLK && mouseEvent->Index < mouseEvent->TotalCount) { record = PhSipReferenceMaxCpuRecord(mouseEvent->Index); } if (record) { PhShowProcessRecordDialog(PhMainWndHandle, record); PhDereferenceProcessRecord(record); } } } else if (ToolStatusConfig.MemGraphEnabled && Header->hwndFrom == MemGraphHandle) { if (mouseEvent->Message == WM_RBUTTONUP) { ShowCustomizeMenu(); } } else if (ToolStatusConfig.CommitGraphEnabled && Header->hwndFrom == CommitGraphHandle) { if (mouseEvent->Message == WM_RBUTTONUP) { ShowCustomizeMenu(); } } else if (ToolStatusConfig.IoGraphEnabled && Header->hwndFrom == IoGraphHandle) { if (mouseEvent->Message == WM_RBUTTONUP) { ShowCustomizeMenu(); } else { if (mouseEvent->Message == WM_LBUTTONDBLCLK && mouseEvent->Index < mouseEvent->TotalCount) { record = PhSipReferenceMaxIoRecord(mouseEvent->Index); } if (record) { PhShowProcessRecordDialog(PhMainWndHandle, record); PhDereferenceProcessRecord(record); } } } } break; } }