Windows
CPU Usage
Threads
Win32 API
Performance Monitoring

How to get the cpu usage per thread on windows win32

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Per-thread CPU usage on Windows is not a single value you can query once from Win32. You have to sample thread execution time at two different moments and compute a percentage from the delta. That is why many first attempts fail: they read raw counters but never turn them into a rate.

What to Measure

The low-level Win32 approach uses GetThreadTimes, which returns:

  • creation time
  • exit time
  • kernel time
  • user time

CPU usage comes from how much kernel-plus-user time increased between two samples, compared with elapsed wall-clock time.

Sampling with GetThreadTimes

Here is a minimal C plus plus example for one thread handle:

cpp
1#include <windows.h>
2#include <iostream>
3
4static ULONGLONG fileTimeToUInt64(const FILETIME& ft) {
5    ULARGE_INTEGER value;
6    value.LowPart = ft.dwLowDateTime;
7    value.HighPart = ft.dwHighDateTime;
8    return value.QuadPart;
9}
10
11double sampleThreadCpuPercent(HANDLE threadHandle, DWORD sleepMs) {
12    FILETIME create1, exit1, kernel1, user1;
13    FILETIME create2, exit2, kernel2, user2;
14
15    if (!GetThreadTimes(threadHandle, &create1, &exit1, &kernel1, &user1)) {
16        return -1.0;
17    }
18
19    ULONGLONG startWall = GetTickCount64();
20    Sleep(sleepMs);
21    ULONGLONG endWall = GetTickCount64();
22
23    if (!GetThreadTimes(threadHandle, &create2, &exit2, &kernel2, &user2)) {
24        return -1.0;
25    }
26
27    ULONGLONG cpu1 = fileTimeToUInt64(kernel1) + fileTimeToUInt64(user1);
28    ULONGLONG cpu2 = fileTimeToUInt64(kernel2) + fileTimeToUInt64(user2);
29
30    ULONGLONG cpuDelta100ns = cpu2 - cpu1;
31    double cpuDeltaMs = cpuDelta100ns / 10000.0;
32    double wallDeltaMs = static_cast<double>(endWall - startWall);
33
34    return (cpuDeltaMs / wallDeltaMs) * 100.0;
35}

This returns the approximate percentage of one logical core consumed by that thread during the sampling window.

Why a Single Sample Is Not Enough

GetThreadTimes gives cumulative CPU time since the thread started. It does not give "current usage" directly.

For example:

  • first sample says thread used 1200 ms total
  • second sample says thread used 1250 ms total
  • wall time between samples is 100 ms

That means the thread used 50 ms of CPU over 100 ms of elapsed time, so the approximate usage is 50 percent of one core.

This rate-based calculation is the essential step.

Getting a Thread Handle

If you already created the thread, you may already have a usable handle. If you only know the thread ID, open it explicitly.

cpp
1HANDLE threadHandle = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, threadId);
2if (!threadHandle) {
3    std::cerr << "OpenThread failed\n";
4}

Be sure to close the handle afterward:

cpp
CloseHandle(threadHandle);

If the thread exits before the second sample, your calculation logic needs to handle that case gracefully.

Sampling Multiple Threads

For a process-wide thread view, enumerate threads, then sample each one over the same interval.

In real tooling, that usually means:

  1. enumerate thread IDs
  2. open handles
  3. capture sample one
  4. wait a fixed interval
  5. capture sample two
  6. compute usage per thread

That is how profilers and diagnostics tools build the numbers you see in UIs.

PDH and Performance Counters

You can also get per-thread data through Performance Data Helper counters, but that path is more complex and often harder to map correctly to a specific thread instance name. For straightforward programmatic diagnostics inside a Win32 tool, GetThreadTimes is usually the better starting point.

If you need historical system-wide monitoring with existing counter infrastructure, PDH may still be appropriate.

Accuracy Considerations

A few things affect the number:

  • sampling interval length
  • scheduler timing
  • timer resolution
  • whether the thread exits or blocks between samples

Very short intervals are noisy. Very long intervals hide spikes. A sampling window around 250 to 1000 milliseconds is a common practical compromise.

Also note that a single thread cannot use more than one core at a time, so the percentage is normally bounded near 100 percent of one logical processor.

Common Pitfalls

  • Expecting Win32 to return instant per-thread CPU percent from a single API call.
  • Reading cumulative thread times without computing deltas between samples.
  • Forgetting that FILETIME units are 100-nanosecond intervals.
  • Sampling over windows that are too short to be stable.
  • Confusing per-thread percent of one core with process percent across all cores.

Summary

  • Use GetThreadTimes to read cumulative user and kernel execution time for a thread.
  • Sample twice and compute CPU usage from the delta over elapsed wall time.
  • Open the thread with the right access rights and close the handle after use.
  • Prefer a reasonable sampling interval instead of extremely short snapshots.
  • Treat per-thread CPU usage as a calculated rate, not a direct stored property.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.