├── .gitignore ├── LICENSE ├── README.md ├── dist └── groupextend.zip ├── entry.cpp ├── entry.h ├── groupextend.rc ├── groupextend.sln ├── groupextend.vcxproj ├── libProcessorGroupExtender ├── LogOut.cpp ├── LogOut.h ├── framework.h ├── groupextend.cpp ├── groupextend.h ├── helpers.cpp ├── helpers.h ├── libProcessorGroupExtender.vcxproj ├── pch.cpp └── pch.h ├── resource.h ├── upload.cmd └── version.h /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /.vs/ 6 | *.aps 7 | *.tlog 8 | *.user 9 | *.filters 10 | /Release/ 11 | /Debug/ 12 | /x64/Debug/ 13 | /x64/Release/ 14 | /libProcessorGroupExtender/Release/ 15 | /libProcessorGroupExtender/Debug/ 16 | /libProcessorGroupExtender/x64/Release/ 17 | /libProcessorGroupExtender/x64/Debug/ 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeremy Collake 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # groupextend 2 | Groupextend allows a processor group unaware application to make use of the entire CPU. It accomplishes this by monitoring the threads of a process and assigning excess threads to supplemental processor groups. 3 | 4 | Group Extender is now exposed as a feature of Process Lasso (https://bitsum.com). To support this project, purchase a license for Process Lasso Pro, or become a GitHub Sponsor. 5 | -------------------------------------------------------------------------------- /dist/groupextend.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremycollake/groupextend/44ed3aad07f053654c793bf6418339b9dca91d82/dist/groupextend.zip -------------------------------------------------------------------------------- /entry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "libProcessorGroupExtender/LogOut.h" 3 | #include "libProcessorGroupExtender/helpers.h" 4 | #include "libProcessorGroupExtender/groupextend.h" 5 | #include "entry.h" 6 | 7 | HANDLE g_hExitEvent = NULL; 8 | 9 | void ShowUsage() 10 | { 11 | wprintf(L"\nUsage: groupextend [pid|name]\n"); 12 | } 13 | 14 | BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) 15 | { 16 | switch (fdwCtrlType) 17 | { 18 | case CTRL_SHUTDOWN_EVENT: 19 | case CTRL_LOGOFF_EVENT: 20 | case CTRL_CLOSE_EVENT: 21 | case CTRL_C_EVENT: 22 | case CTRL_BREAK_EVENT: 23 | wprintf(L"\n > Ctrl event"); 24 | if (g_hExitEvent) SetEvent(g_hExitEvent); 25 | return TRUE; 26 | default: 27 | return FALSE; 28 | } 29 | } 30 | 31 | int wmain(int argc, const wchar_t* argv[]) 32 | { 33 | LogOut Log(GroupExtend::DefaultLogTarget); 34 | Log.Write(GroupExtend::INTRO_STRING); 35 | Log.Write(GroupExtend::BUILD_STRING_FMT, GroupExtend::BUILD_NUM_STR, __DATE__); 36 | Log.Write(L"\n"); 37 | 38 | if (argc < 2) 39 | { 40 | ShowUsage(); 41 | return 1; 42 | } 43 | 44 | // try to resolve command line argument(s) to PIDs from exeName 45 | // if that fails, assume is numeric PID 46 | // this allows for processes with exeNames of integers (if that ever happens) 47 | std::vector vecTargetPIDs; 48 | for (int i = 1; i < argc; i++) 49 | { 50 | if (GroupExtend::GetPIDsForProcessName(argv[i], vecTargetPIDs)) 51 | { 52 | Log.Write(L"\n%s has instances of PID(s)", argv[i]); 53 | for (auto& pid : vecTargetPIDs) 54 | { 55 | Log.Write(L" %u", pid); 56 | } 57 | } 58 | else 59 | { 60 | unsigned long pid = wcstoul(argv[i], nullptr, 10); 61 | if (pid) 62 | { 63 | vecTargetPIDs.push_back(pid); 64 | } 65 | } 66 | } 67 | 68 | if (vecTargetPIDs.size() == 0) 69 | { 70 | Log.Write(L"\nERROR: No processes found that match specification.\n"); 71 | return 2; 72 | } 73 | 74 | if (vecTargetPIDs.size() > 1) 75 | { 76 | Log.Write(L"\nWARNING: Multiple process instances were found, but groupextend can currently only manage one (per instance). Managing %u", vecTargetPIDs[0]); 77 | } 78 | 79 | // required priv tokens, by name 80 | const WCHAR* pwszSecTokens[] = 81 | { 82 | SE_ASSIGNPRIMARYTOKEN_NAME, 83 | SE_DEBUG_NAME, 84 | SE_INC_BASE_PRIORITY_NAME 85 | }; 86 | 87 | for (const WCHAR* pwszToken : pwszSecTokens) 88 | { 89 | if (!GroupExtend::NtGetPrivByName(pwszToken)) 90 | { 91 | Log.Write(L"\n WARNING: Couldn't get privilege %s", pwszToken); 92 | } 93 | } 94 | 95 | SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); 96 | //SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); 97 | 98 | // create before SetConsoelCtrlHandler 99 | g_hExitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); 100 | 101 | SetConsoleCtrlHandler(CtrlHandler, TRUE); 102 | 103 | // for signalling caller that thread stopped (e.g. process no longer exists or error) 104 | HANDLE hThreadStoppedEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); 105 | 106 | if (!g_hExitEvent || !hThreadStoppedEvent) 107 | { 108 | Log.Write(L"\n ERROR creating events. Aborting"); 109 | if (hThreadStoppedEvent) CloseHandle(hThreadStoppedEvent); 110 | if (g_hExitEvent) CloseHandle(g_hExitEvent); 111 | return 3; 112 | } 113 | 114 | // 115 | // start management of target process threads 116 | // magic is in libProcessorGroupExtender 117 | // 118 | ProcessorGroupExtender_SingleProcess cExtender; 119 | if (cExtender.StartAsync(vecTargetPIDs[0], 0, GroupExtend::DefaultLogTarget, hThreadStoppedEvent)) 120 | { 121 | HANDLE hWaits[2] = { g_hExitEvent, hThreadStoppedEvent }; 122 | WaitForMultipleObjects(_countof(hWaits), hWaits, FALSE, INFINITE); 123 | cExtender.Stop(); 124 | } 125 | 126 | CloseHandle(hThreadStoppedEvent); 127 | CloseHandle(g_hExitEvent); 128 | return 0; 129 | } 130 | -------------------------------------------------------------------------------- /entry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "version.h" 5 | 6 | void ShowUsage(); 7 | BOOL WINAPI CtrlHandler(DWORD fdwCtrlType); 8 | int wmain(int argc, const wchar_t* argv[]); 9 | 10 | namespace GroupExtend 11 | { 12 | const WCHAR* const BUILD_NUM_STR = CURRENT_VERSION; 13 | const unsigned short INVALID_GROUP_ID = 256; 14 | const WCHAR* const INTRO_STRING = L"\ngroupextend, (c)2020 Jeremy Collake , https://bitsum.com"; 15 | const WCHAR* const BUILD_STRING_FMT = L"\nbuild %s date %hs"; 16 | const LogOut::LOG_TARGET DefaultLogTarget = LogOut::LTARGET_STDOUT; 17 | } -------------------------------------------------------------------------------- /groupextend.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | #include "version.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | #pragma code_page(1252) 22 | 23 | #ifdef APSTUDIO_INVOKED 24 | ///////////////////////////////////////////////////////////////////////////// 25 | // 26 | // TEXTINCLUDE 27 | // 28 | 29 | 1 TEXTINCLUDE 30 | BEGIN 31 | "resource.h\0" 32 | END 33 | 34 | 2 TEXTINCLUDE 35 | BEGIN 36 | "#include ""winres.h""\r\n" 37 | "\0" 38 | END 39 | 40 | 3 TEXTINCLUDE 41 | BEGIN 42 | "\r\n" 43 | "\0" 44 | END 45 | 46 | #endif // APSTUDIO_INVOKED 47 | 48 | 49 | ///////////////////////////////////////////////////////////////////////////// 50 | // 51 | // Version 52 | // 53 | 54 | VS_VERSION_INFO VERSIONINFO 55 | FILEVERSION 1,0,0,1 56 | PRODUCTVERSION 1,0,0,1 57 | FILEFLAGSMASK 0x3fL 58 | #ifdef _DEBUG 59 | FILEFLAGS 0x1L 60 | #else 61 | FILEFLAGS 0x0L 62 | #endif 63 | FILEOS 0x40004L 64 | FILETYPE 0x1L 65 | FILESUBTYPE 0x0L 66 | BEGIN 67 | BLOCK "StringFileInfo" 68 | BEGIN 69 | BLOCK "040904b0" 70 | BEGIN 71 | VALUE "FileDescription", "GroupExtend" 72 | VALUE "InternalName", "GroupExtend" 73 | VALUE "OriginalFilename", "groupext.exe" 74 | VALUE "ProductName", "GroupExtend" 75 | VALUE "CompanyName", RESOURCE_COMPANY_NAME 76 | VALUE "FileVersion", RESOURCE_FILE_VERSION 77 | VALUE "LegalCopyright", RESOURCE_LEGAL_COPYRIGHT 78 | VALUE "ProductVersion", RESOURCE_PRODUCT_VERSION 79 | END 80 | END 81 | BLOCK "VarFileInfo" 82 | BEGIN 83 | VALUE "Translation", 0x409, 1200 84 | END 85 | END 86 | 87 | #endif // English (United States) resources 88 | ///////////////////////////////////////////////////////////////////////////// 89 | 90 | 91 | 92 | #ifndef APSTUDIO_INVOKED 93 | ///////////////////////////////////////////////////////////////////////////// 94 | // 95 | // Generated from the TEXTINCLUDE 3 resource. 96 | // 97 | 98 | 99 | ///////////////////////////////////////////////////////////////////////////// 100 | #endif // not APSTUDIO_INVOKED 101 | 102 | -------------------------------------------------------------------------------- /groupextend.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29728.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "groupextend", "groupextend.vcxproj", "{1526BC87-BEE5-4C42-8BD5-A94FDC94692C}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libProcessorGroupExtender", "libProcessorGroupExtender\libProcessorGroupExtender.vcxproj", "{F31C257F-8245-427A-97A5-669B0BB8D907}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {1526BC87-BEE5-4C42-8BD5-A94FDC94692C}.Debug|x64.ActiveCfg = Debug|x64 19 | {1526BC87-BEE5-4C42-8BD5-A94FDC94692C}.Debug|x64.Build.0 = Debug|x64 20 | {1526BC87-BEE5-4C42-8BD5-A94FDC94692C}.Debug|x86.ActiveCfg = Debug|Win32 21 | {1526BC87-BEE5-4C42-8BD5-A94FDC94692C}.Debug|x86.Build.0 = Debug|Win32 22 | {1526BC87-BEE5-4C42-8BD5-A94FDC94692C}.Release|x64.ActiveCfg = Release|x64 23 | {1526BC87-BEE5-4C42-8BD5-A94FDC94692C}.Release|x64.Build.0 = Release|x64 24 | {1526BC87-BEE5-4C42-8BD5-A94FDC94692C}.Release|x86.ActiveCfg = Release|Win32 25 | {1526BC87-BEE5-4C42-8BD5-A94FDC94692C}.Release|x86.Build.0 = Release|Win32 26 | {F31C257F-8245-427A-97A5-669B0BB8D907}.Debug|x64.ActiveCfg = Debug|x64 27 | {F31C257F-8245-427A-97A5-669B0BB8D907}.Debug|x64.Build.0 = Debug|x64 28 | {F31C257F-8245-427A-97A5-669B0BB8D907}.Debug|x86.ActiveCfg = Debug|Win32 29 | {F31C257F-8245-427A-97A5-669B0BB8D907}.Debug|x86.Build.0 = Debug|Win32 30 | {F31C257F-8245-427A-97A5-669B0BB8D907}.Release|x64.ActiveCfg = Release|x64 31 | {F31C257F-8245-427A-97A5-669B0BB8D907}.Release|x64.Build.0 = Release|x64 32 | {F31C257F-8245-427A-97A5-669B0BB8D907}.Release|x86.ActiveCfg = Release|Win32 33 | {F31C257F-8245-427A-97A5-669B0BB8D907}.Release|x86.Build.0 = Release|Win32 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {476DDCDA-FA9F-4C7E-82A3-29424273B62A} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /groupextend.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {1526BC87-BEE5-4C42-8BD5-A94FDC94692C} 24 | Win32Proj 25 | groupextend 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | 87 | 88 | 89 | Level3 90 | true 91 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | true 93 | 94 | 95 | Console 96 | true 97 | HighestAvailable 98 | 99 | 100 | 101 | 102 | 103 | 104 | Level3 105 | true 106 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 107 | true 108 | 109 | 110 | Console 111 | true 112 | HighestAvailable 113 | 114 | 115 | 116 | 117 | 118 | 119 | Level3 120 | true 121 | true 122 | true 123 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 124 | true 125 | MultiThreaded 126 | 127 | 128 | Console 129 | true 130 | true 131 | true 132 | HighestAvailable 133 | 134 | 135 | 136 | 137 | 138 | 139 | Level3 140 | true 141 | true 142 | true 143 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 144 | true 145 | MultiThreaded 146 | 147 | 148 | Console 149 | true 150 | true 151 | true 152 | HighestAvailable 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | {f31c257f-8245-427a-97a5-669b0bb8d907} 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /libProcessorGroupExtender/LogOut.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * (c)2019 Jeremy Collake , Bitsum LLC 3 | */ 4 | #include "pch.h" 5 | #include "LogOut.h" 6 | 7 | LogOut::LogOut(const LOG_TARGET target) : logTarget(target) 8 | { 9 | } 10 | 11 | void LogOut::SetTarget(const LOG_TARGET target) 12 | { 13 | logTarget = target; 14 | } 15 | 16 | void LogOut::Write(LPCTSTR fmt, ...) 17 | { 18 | va_list marker; 19 | TCHAR szBuf[4096] = { 0 }; 20 | va_start(marker, fmt); 21 | wvsprintf(szBuf, fmt, marker); 22 | va_end(marker); 23 | 24 | CString csTemp; 25 | csTemp.Format(L"%s", szBuf); 26 | switch (logTarget) 27 | { 28 | case LTARGET_STDOUT: 29 | wprintf(L"%s", csTemp.GetBuffer()); 30 | break; 31 | case LTARGET_FILE: 32 | // fall-through until implemented 33 | //break; 34 | case LTARGET_DEBUG: 35 | csTemp.Remove('\n'); 36 | csTemp.Remove('\r'); 37 | MyDebugOutput(csTemp); 38 | break; 39 | case LTARGET_NONE: 40 | default: 41 | break; 42 | } 43 | } 44 | 45 | void LogOut::FormattedErrorOut(const WCHAR* msg) 46 | { 47 | DWORD eNum; 48 | TCHAR sysMsg[256]; 49 | TCHAR* p; 50 | 51 | eNum = GetLastError(); 52 | FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 53 | NULL, eNum, 54 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language 55 | sysMsg, 256, NULL); 56 | 57 | // Trim the end of the line and terminate it with a null 58 | p = sysMsg; 59 | while ((*p > 31) || (*p == 9)) 60 | ++p; 61 | do { *p-- = 0; } while ((p >= sysMsg) && 62 | ((*p == '.') || (*p < 33))); 63 | 64 | Write(L"\n WARNING: %s failed with error %d (%s)", msg, eNum, sysMsg); 65 | } -------------------------------------------------------------------------------- /libProcessorGroupExtender/LogOut.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | //#define DEBUG_TRACING 7 | 8 | #if defined(DEBUG) || defined(DEBUG_TRACING) 9 | #define MyDebugOutput OutputDebugString 10 | #else 11 | #define MyDebugOutput 12 | #endif 13 | 14 | // output to log or debug 15 | class LogOut 16 | { 17 | public: 18 | enum LOG_TARGET 19 | { 20 | LTARGET_NONE = 0, 21 | LTARGET_DEBUG, 22 | LTARGET_STDOUT, 23 | LTARGET_FILE 24 | }; 25 | LOG_TARGET logTarget; 26 | 27 | LogOut(const LOG_TARGET logTarget = LTARGET_STDOUT); 28 | 29 | void SetTarget(const LOG_TARGET logTarget); 30 | 31 | void Write(LPCTSTR fmt, ...); 32 | 33 | void FormattedErrorOut(LPCTSTR msg); 34 | }; 35 | -------------------------------------------------------------------------------- /libProcessorGroupExtender/framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 4 | 5 | #include 6 | #include 7 | -------------------------------------------------------------------------------- /libProcessorGroupExtender/groupextend.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * (c)2022 Jeremy Collake , Bitsum LLC 3 | */ 4 | #include "pch.h" 5 | #include "groupextend.h" 6 | #include "framework.h" 7 | #include "helpers.h" 8 | #include "LogOut.h" 9 | #include "../version.h" 10 | #include "../entry.h" 11 | 12 | // async wrapper implemented in header file 13 | 14 | // this mutex necessary due to thread-safety quirk in the Toolhelp library, 15 | // despite each thread obtaining a new and distinct snapshot. 16 | // See https://github.com/jeremycollake/groupextend/issues/5 17 | std::mutex g_mutex_ToolhelpSnapshots; 18 | 19 | // the meat 20 | int ProcessorGroupExtender_SingleProcess::ExtendGroupForProcess() 21 | { 22 | m_log.Write(L"\n Monitoring process %u with refresh rate of %u ms", m_pid, m_nRefreshRateMs); 23 | 24 | _ASSERT(m_pid && m_nRefreshRateMs >= GroupExtend::REFRESH_MINIMUM_ALLOWED_MS); 25 | 26 | srand(static_cast(time(nullptr))); 27 | 28 | unsigned short nActiveGroupCount = static_cast(GetActiveProcessorGroupCount()); 29 | if (nActiveGroupCount < 2) 30 | { 31 | m_log.Write(L"\n ERROR: Active processor groups is only %u. Nothing to do, aborting.", nActiveGroupCount); 32 | if (m_hThreadStoppedEvent) SetEvent(m_hThreadStoppedEvent); 33 | return 2; 34 | } 35 | 36 | // should be equal size groups, but check each group to support theoretical variance in size 37 | std::vector vecProcessorsPerGroup; 38 | std::vector vecAllCPUMaskPerGroup; 39 | for (unsigned short curGroup = 0; curGroup < nActiveGroupCount; curGroup++) 40 | { 41 | // store the processor count for this group 42 | vecProcessorsPerGroup.push_back(GetActiveProcessorCount(curGroup)); 43 | // and the all CPU affinity mask for this group 44 | vecAllCPUMaskPerGroup.push_back(GroupExtend::BuildAffinityMask(vecProcessorsPerGroup[curGroup])); 45 | } 46 | 47 | // now get group info for target process 48 | std::vector vecGroupsThisProcess; 49 | if (!GroupExtend::GetProcessProcessorGroups(m_pid, vecGroupsThisProcess)) 50 | { 51 | m_log.Write(L"\n ERROR: GetProcessProcessorGroups returned 0. Aborting."); 52 | if (m_hThreadStoppedEvent) SetEvent(m_hThreadStoppedEvent); 53 | return 3; 54 | } 55 | if (vecGroupsThisProcess.size() > 1) 56 | { 57 | m_log.Write(L"\n WARNING: Process is already multi-group! This algorithm may place threads on groups the application doesn't expect."); 58 | // continue on, debatably 59 | } 60 | m_log.Write(L"\n Process currently has threads on group(s)"); 61 | for (auto& i : vecGroupsThisProcess) 62 | { 63 | m_log.Write(L" %u", i); 64 | } 65 | unsigned short nDefaultGroupId = vecGroupsThisProcess[0]; 66 | 67 | // track all thread assignments to processor groups (all threads in the managed app) 68 | std::map mapThreadIDsToProcessorGroupNum; 69 | 70 | // keep count of threads per group (group ID is index) 71 | std::vector vecThreadCountPerGroup; 72 | 73 | // initialize vector 74 | for (unsigned short n = 0; n < nActiveGroupCount; n++) 75 | { 76 | vecThreadCountPerGroup.push_back(0); 77 | } 78 | 79 | do 80 | { 81 | std::lock_guard lock(g_mutex_ToolhelpSnapshots); 82 | 83 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); 84 | if (hSnapshot == INVALID_HANDLE_VALUE) 85 | { 86 | m_log.FormattedErrorOut(L" CreateToolhelp32Snapshot"); 87 | return 3; 88 | } 89 | THREADENTRY32 te32; 90 | te32.dwSize = sizeof(THREADENTRY32); 91 | if (!Thread32First(hSnapshot, &te32)) 92 | { 93 | m_log.FormattedErrorOut(L" Thread32First"); 94 | CloseHandle(hSnapshot); 95 | return 4; 96 | } 97 | 98 | // for marking each thread ID as found so we can identify deleted threads and remove from mapThreadIDsToProcessorGroupNum 99 | std::map mapThreadIDsFoundThisEnum; 100 | do 101 | { 102 | if (m_pid == te32.th32OwnerProcessID) 103 | { 104 | mapThreadIDsFoundThisEnum[te32.th32ThreadID] = true; 105 | } 106 | } while (Thread32Next(hSnapshot, &te32)); 107 | 108 | // remove deleted threads 109 | std::vector vecPendingThreadIDDeletions; 110 | for (auto& i : mapThreadIDsToProcessorGroupNum) 111 | { 112 | if (mapThreadIDsFoundThisEnum.find(i.first) == mapThreadIDsFoundThisEnum.end()) 113 | { 114 | vecPendingThreadIDDeletions.push_back(i.first); 115 | } 116 | } 117 | for (auto& i : vecPendingThreadIDDeletions) 118 | { 119 | unsigned short nGroupId = mapThreadIDsToProcessorGroupNum.find(i)->second; 120 | vecThreadCountPerGroup[nGroupId]--; 121 | mapThreadIDsToProcessorGroupNum.erase(i); 122 | m_log.Write(L"\n Thread %u terminated on group %u", i, nGroupId); 123 | } 124 | // add new threads 125 | std::vector vecPendingThreadIDAdditions; 126 | for (auto& i : mapThreadIDsFoundThisEnum) 127 | { 128 | if (mapThreadIDsToProcessorGroupNum.find(i.first) == mapThreadIDsToProcessorGroupNum.end()) 129 | { 130 | vecPendingThreadIDAdditions.push_back(i.first); 131 | } 132 | } 133 | for (auto& i : vecPendingThreadIDAdditions) 134 | { 135 | // randomly spread threads across available processor groups 136 | unsigned short nGroupId = rand() % nActiveGroupCount; 137 | 138 | HANDLE hThread = OpenThread(THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION, FALSE, i); 139 | if (hThread) 140 | { 141 | GROUP_AFFINITY grpAffinity = {}; 142 | grpAffinity.Group = nGroupId; 143 | grpAffinity.Mask = static_cast(vecAllCPUMaskPerGroup[nGroupId]); 144 | DWORD_PTR dwPriorAffinity = SetThreadGroupAffinity(hThread, &grpAffinity, nullptr); 145 | CloseHandle(hThread); 146 | if (!dwPriorAffinity) 147 | { 148 | // error, so leave in default group 149 | nGroupId = nDefaultGroupId; 150 | m_log.Write(L"\n WARNING: Error setting thread affinity for %u (terminated too quick?). Leaving in default group.", i); 151 | } 152 | } 153 | else 154 | { 155 | // no access, so leave in default group 156 | nGroupId = nDefaultGroupId; 157 | m_log.Write(L"\n WARNING: No access to thread %u. Leaving in default group.", i); 158 | } 159 | 160 | m_log.Write(L"\n Thread %u found, group %u", i, nGroupId); 161 | vecThreadCountPerGroup[nGroupId]++; 162 | mapThreadIDsToProcessorGroupNum[i] = nGroupId; 163 | } 164 | 165 | m_log.Write(L"\n Managing %u threads", mapThreadIDsToProcessorGroupNum.size()); 166 | for (unsigned short n = 0; n < nActiveGroupCount; n++) 167 | { 168 | m_log.Write(L"\n Group %u has %u threads", n, vecThreadCountPerGroup[n]); 169 | } 170 | 171 | CloseHandle(hSnapshot); 172 | 173 | if (!mapThreadIDsToProcessorGroupNum.size()) 174 | { 175 | m_log.Write(L"\n No threads to manage, exiting ..."); 176 | break; 177 | } 178 | } while (WaitForSingleObject(m_hQuitNotifyEvent, m_nRefreshRateMs) == WAIT_TIMEOUT); 179 | 180 | // signal caller that our thread stopped 181 | if (m_hThreadStoppedEvent) SetEvent(m_hThreadStoppedEvent); 182 | return 0; 183 | } -------------------------------------------------------------------------------- /libProcessorGroupExtender/groupextend.h: -------------------------------------------------------------------------------- 1 | /* 2 | * (c)2020 Jeremy Collake , Bitsum LLC 3 | */ 4 | #pragma once 5 | 6 | #include "LogOut.h" 7 | #include 8 | #include 9 | 10 | namespace GroupExtend 11 | { 12 | const unsigned int REFRESH_MINIMUM_ALLOWED_MS = 100; 13 | const unsigned int REFRESH_MS = 1000; 14 | } 15 | 16 | // intended to manage only a single process per group extension instance 17 | class ProcessorGroupExtender_SingleProcess 18 | { 19 | std::thread m_hExtenderThread; 20 | HANDLE m_hQuitNotifyEvent; // for signalling thread to stop 21 | HANDLE m_hThreadStoppedEvent; // for signalling caller thread stopped (prematurely) 22 | unsigned long m_pid; 23 | LogOut m_log; 24 | unsigned long m_nRefreshRateMs; 25 | 26 | int ExtendGroupForProcess(); 27 | public: 28 | ProcessorGroupExtender_SingleProcess() : 29 | m_pid(0), 30 | m_nRefreshRateMs(GroupExtend::REFRESH_MS), 31 | m_hQuitNotifyEvent(nullptr), 32 | m_hThreadStoppedEvent(nullptr) 33 | {} 34 | ~ProcessorGroupExtender_SingleProcess() 35 | { 36 | if (IsActive()) 37 | { 38 | Stop(); 39 | } 40 | if (m_hQuitNotifyEvent) 41 | { 42 | CloseHandle(m_hQuitNotifyEvent); 43 | } 44 | } 45 | bool IsActive() 46 | { 47 | return m_hExtenderThread.joinable(); 48 | } 49 | bool StartAsync(unsigned long pid, unsigned long nRefreshRateMs, LogOut::LOG_TARGET logTarget, HANDLE hThreadStoppedEvent) 50 | { 51 | if (IsActive()) 52 | { 53 | return false; 54 | } 55 | m_pid = pid; 56 | m_log.SetTarget(logTarget); 57 | m_hThreadStoppedEvent = hThreadStoppedEvent; 58 | // if refresh rate param is above minimum, use it - otherwise use default 59 | if (nRefreshRateMs >= GroupExtend::REFRESH_MINIMUM_ALLOWED_MS) 60 | { 61 | m_nRefreshRateMs = nRefreshRateMs; 62 | } 63 | else 64 | { 65 | m_nRefreshRateMs = GroupExtend::REFRESH_MS; 66 | } 67 | m_hQuitNotifyEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); 68 | m_hExtenderThread = std::thread(&ProcessorGroupExtender_SingleProcess::ExtendGroupForProcess, this); 69 | return true; 70 | } 71 | bool Stop() 72 | { 73 | if (IsActive()) 74 | { 75 | _ASSERT(m_hQuitNotifyEvent); 76 | SetEvent(m_hQuitNotifyEvent); 77 | m_hExtenderThread.join(); 78 | CloseHandle(m_hQuitNotifyEvent); 79 | m_hQuitNotifyEvent = nullptr; 80 | return true; 81 | } 82 | return false; 83 | } 84 | }; 85 | 86 | -------------------------------------------------------------------------------- /libProcessorGroupExtender/helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "helpers.h" 3 | 4 | namespace GroupExtend 5 | { 6 | // resolve process name to PID(s) 7 | unsigned int GetPIDsForProcessName(const WCHAR* pwszBaseName, std::vector& vFoundPids) 8 | { 9 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 10 | if (hSnapshot == INVALID_HANDLE_VALUE) 11 | { 12 | return 0; 13 | } 14 | 15 | if (hSnapshot) 16 | { 17 | PROCESSENTRY32 pe32; 18 | pe32.dwSize = sizeof(PROCESSENTRY32); 19 | if (!Process32First(hSnapshot, &pe32)) 20 | { 21 | CloseHandle(hSnapshot); 22 | return(FALSE); 23 | } 24 | do 25 | { 26 | if (0 == _wcsnicmp(pwszBaseName, pe32.szExeFile, _countof(pe32.szExeFile))) 27 | { 28 | vFoundPids.push_back(pe32.th32ProcessID); 29 | } 30 | } while (Process32Next(hSnapshot, &pe32)); 31 | 32 | CloseHandle(hSnapshot); 33 | } 34 | 35 | return static_cast(vFoundPids.size()); 36 | } 37 | 38 | // return value is count of processor groups. Returns list of groups associated with this process 39 | unsigned int GetProcessProcessorGroups(const unsigned long pid, std::vector& vGroups) 40 | { 41 | vGroups.clear(); 42 | 43 | HANDLE hProcess = NULL; 44 | hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); 45 | if (!hProcess) 46 | { 47 | return 0; 48 | } 49 | unsigned short nGroupCount = 0; 50 | unsigned short* pGroupArray = NULL; 51 | // get the required buffer size 52 | if (FALSE != GetProcessGroupAffinity(hProcess, &nGroupCount, NULL) 53 | || 54 | ERROR_INSUFFICIENT_BUFFER != GetLastError()) 55 | { 56 | return 0; 57 | } 58 | pGroupArray = new unsigned short[nGroupCount]; 59 | if (FALSE == GetProcessGroupAffinity(hProcess, &nGroupCount, pGroupArray)) 60 | { 61 | return 0; 62 | } 63 | // got the groups, populate vector and return 64 | for (unsigned short nI = 0; nI < nGroupCount; nI++) 65 | { 66 | vGroups.push_back(pGroupArray[nI]); 67 | } 68 | delete pGroupArray; 69 | 70 | return static_cast(vGroups.size()); 71 | } 72 | 73 | // build CPU affinity mask for X processors 74 | unsigned long long BuildAffinityMask(const unsigned int nProcessors) 75 | { 76 | unsigned long long bitmaskAffinity = 0; 77 | for (unsigned int n = 0; n < nProcessors; n++) 78 | { 79 | bitmaskAffinity |= (1ULL << n); 80 | } 81 | return bitmaskAffinity; 82 | } 83 | 84 | 85 | bool NtGetPrivByName(const WCHAR* pwszPrivName) 86 | { 87 | HANDLE hToken; 88 | TOKEN_PRIVILEGES tkp; 89 | if (!OpenProcessToken(GetCurrentProcess(), 90 | TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 91 | { 92 | return false; 93 | } 94 | LookupPrivilegeValue(NULL, pwszPrivName, &tkp.Privileges[0].Luid); 95 | tkp.PrivilegeCount = 1; 96 | tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 97 | return AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0) ? true : false; 98 | } 99 | } -------------------------------------------------------------------------------- /libProcessorGroupExtender/helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace GroupExtend 4 | { 5 | // resolve process name to PID(s) 6 | unsigned int GetPIDsForProcessName(const WCHAR* pwszBaseName, std::vector& vFoundPids); 7 | // return value is count of processor groups. Returns list of groups associated with this process 8 | unsigned int GetProcessProcessorGroups(const unsigned long pid, std::vector& vGroups); 9 | // build CPU affinity mask for X processors 10 | unsigned long long BuildAffinityMask(const unsigned int nProcessors); 11 | 12 | // privilege acquistion 13 | bool NtGetPrivByName(const WCHAR* ptszPrivName); 14 | } -------------------------------------------------------------------------------- /libProcessorGroupExtender/libProcessorGroupExtender.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {F31C257F-8245-427A-97A5-669B0BB8D907} 24 | Win32Proj 25 | libProcessorGroupExtender 26 | 10.0 27 | 28 | 29 | 30 | StaticLibrary 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | StaticLibrary 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | StaticLibrary 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | StaticLibrary 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | false 75 | 76 | 77 | true 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Use 88 | Level3 89 | true 90 | true 91 | true 92 | NDEBUG;_LIB;%(PreprocessorDefinitions) 93 | true 94 | pch.h 95 | MultiThreaded 96 | 97 | 98 | Windows 99 | true 100 | true 101 | true 102 | 103 | 104 | 105 | 106 | Use 107 | Level3 108 | true 109 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 110 | true 111 | pch.h 112 | MultiThreadedDebug 113 | 114 | 115 | Windows 116 | true 117 | 118 | 119 | 120 | 121 | Use 122 | Level3 123 | true 124 | _DEBUG;_LIB;%(PreprocessorDefinitions) 125 | true 126 | pch.h 127 | MultiThreadedDebug 128 | 129 | 130 | Windows 131 | true 132 | 133 | 134 | 135 | 136 | Use 137 | Level3 138 | true 139 | true 140 | true 141 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 142 | true 143 | pch.h 144 | MultiThreaded 145 | 146 | 147 | Windows 148 | true 149 | true 150 | true 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | Create 166 | Create 167 | Create 168 | Create 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /libProcessorGroupExtender/pch.cpp: -------------------------------------------------------------------------------- 1 | // pch.cpp: source file corresponding to the pre-compiled header 2 | 3 | #include "pch.h" 4 | 5 | // When you are using pre-compiled headers, this source file is necessary for compilation to succeed. 6 | -------------------------------------------------------------------------------- /libProcessorGroupExtender/pch.h: -------------------------------------------------------------------------------- 1 | // pch.h: This is a precompiled header file. 2 | // Files listed below are compiled only once, improving build performance for future builds. 3 | // This also affects IntelliSense performance, including code completion and many code browsing features. 4 | // However, files listed here are ALL re-compiled if any one of them is updated between builds. 5 | // Do not add files here that you will be updating frequently as this negates the performance advantage. 6 | 7 | #ifndef PCH_H 8 | #define PCH_H 9 | 10 | // add headers that you want to pre-compile here 11 | #include "framework.h" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #endif //PCH_H 18 | -------------------------------------------------------------------------------- /resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by groupextend.rc 4 | 5 | // Next default values for new objects 6 | // 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NEXT_RESOURCE_VALUE 101 10 | #define _APS_NEXT_COMMAND_VALUE 40001 11 | #define _APS_NEXT_CONTROL_VALUE 1001 12 | #define _APS_NEXT_SYMED_VALUE 101 13 | #endif 14 | #endif 15 | -------------------------------------------------------------------------------- /upload.cmd: -------------------------------------------------------------------------------- 1 | call bitsum-sign x64\release\groupextend.exe 2 | pkzipc -add dist\groupextend.zip x64\release\groupextend.exe 3 | scp dist\groupextend.zip jeremy@az.bitsum.com:/var/www/vhosts/bitsum.com/files/groupextend.zip -------------------------------------------------------------------------------- /version.h: -------------------------------------------------------------------------------- 1 | #define CURRENT_VERSION L"0.0.0.12" 2 | #define RESOURCE_FILE_VERSION 0,0,0,12 3 | #define RAW_FILE_VERSION 0,0,0,12 4 | #define RAW_PRODUCT_VERSION RAW_FILE_VERSION 5 | #define RESOURCE_PRODUCT_VERSION RESOURCE_FILE_VERSION 6 | #define RESOURCE_LEGAL_COPYRIGHT "Copyright (c)2020 Jeremy Collake" 7 | #define RESOURCE_COMPANY_NAME "Bitsum LLC" 8 | --------------------------------------------------------------------------------