2025-05-13 19:45:22 +03:00

662 lines
23 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#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;
}
}