Time a program on Windows

Since Windows 10 and 11 cmd.exe does not have a built-in function to time an executable analogous to Unix time, I had ChatGPT write a C program compilable with gcc to do it that appears to work.

/*
xtimer.c

Run a command-line program as a child process and print the elapsed
wall-clock time in seconds after the child finishes.

The program expects at least one command-line argument: the name of the
program to run. Any additional arguments are passed to that program.
It builds a command line from argv[1..], starts the child with
CreateProcessA, waits for it to finish with WaitForSingleObject, and
measures elapsed time with QueryPerformanceCounter and
QueryPerformanceFrequency.

If the child process returns a nonzero exit code, that code is also
printed, and xtimer returns the same exit code to the caller.

This is a Windows program and uses the Win32 API.
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv
)
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
LARGE_INTEGER freq, t0, t1;
char *cmdline;
size_t len;
int i;
DWORD exit_code;

if (argc < 2) {
	fprintf(stderr, "usage: %s program.exe [args...]\ncompiled from xtimer.c\n", argv[0]);
	return 1;
}

/* build command line from argv[1..] */
len = 0;
for (i = 1; i < argc; i++) {
	len += strlen(argv[i]) + 3;
}

cmdline = (char *)malloc(len + 1);
if (cmdline == NULL) {
	fprintf(stderr, "memory allocation failed\n");
	return 1;
}

cmdline[0] = '\0';
for (i = 1; i < argc; i++) {
	if (i > 1) {
		strcat(cmdline, " ");
	}
	strcat(cmdline, argv[i]);
}

ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));

QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&t0);

if (!CreateProcessA(
		    NULL,
		    cmdline,
		    NULL,
		    NULL,
		    FALSE,
		    0,
		    NULL,
		    NULL,
		    &si,
		    &pi)) {
	fprintf(stderr, "CreateProcess failed, error %lu\n", GetLastError());
	free(cmdline);
	return 1;
}

WaitForSingleObject(pi.hProcess, INFINITE);
QueryPerformanceCounter(&t1);

GetExitCodeProcess(pi.hProcess, &exit_code);

printf("\ntime elapsed (s): %.4f",
       (double)(t1.QuadPart - t0.QuadPart) / (double)freq.QuadPart);
if (exit_code) printf(" -- exit code = %lu\n", (unsigned long)exit_code);

CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
free(cmdline);

return (int)exit_code;

}

Saving the executable as timer.exe and running it on an executable gives output such as

timer.exe fit.exe

…

times (s)
         total     read data  calc moments           fit         print
      3.171000      3.156000      0.000000      0.000000      0.015000

time elapsed (s): 3.2208

where the first set of times was generated in the Fortran program using system_clock. Wrapping an executable with timer.exe may add 0.05s to the run time.

2 Likes

I recommend using the shell on Windows: GitHub - prefix-dev/shell: The ultimate cross-platform, bash-like shell · GitHub, which has a time command.

1 Like

A nice straight-forward wallclock timer. Odd something like that was
never added to the cmd() environment.

Alternatively to measure the elapsed time for a command on Windows,
I am told you can use PowerShell’s Measure-Command.

I am not a PowerShell power user so I find the PowerShell syntax very
awkward and prefer the more familiar (at least to me) CLI interface your
program provides.

In the past I remember using DOS scripts that used the %TIME% variable
to provide a wallclock timer. I bet some of those are still out there
or AI could generate a “.bat” file that would do wallclock timing.

Related information for GNU/Linux

On GNU/Linux systems, which I am far more familiar with,
if I want a Fortran program (or bash shell, or …)
to report metrics without requiring using something like

    /bin/time -v $CMD

I find having the program open files like /proc/$PPID/status and parse
out fields or even just do something like

   program testit
   ! show a bunch of process information about the current program
   call execute_command_line("cat /proc/$PPID/status")
   ! use the same file but just extract memory usage
   call execute_command_line("egrep '^Name:|^VMsize:|^VmRSS:' /proc/$PPID/status")
   ! ps(1) lets you select many metrics such as user, system, and wallclock time
   call execute_command_line("ps -p $PPID -o cutime,cstime,time")
   end program testit

is easier than making all the required C calls as I usually want User CPU,
System CPU, context switching information, wallclock, memory HWM … at
a minimum, and it is all out there in files on Unix variants. Since the
/proc files are accessible from any language it is easy to create a bash
shell to show the information and just call that from EXECUTE_COMMAND_LINE().

If you use cgroups (quite commonly used on HPC clusters in particular)
there are several files showing the most important metrics for your
overall session available. A lot of batch scheduling environments can
generate metric reports as well.

ps(1) has a lot of options on most systems that accesses a lot of the
information in the /proc/$$/* files. Calling the ps(1) command
you can get virtually all the information that the time(1) command provides
(note that /bin/time -v $CMD usually provides far more information than
“time $CMD” displays, which usually runs a shell built-in instead of
the actual time(1) command).

   /bin/time -v date
Sun Mar 22 23:00:52 EDT 2026
	Command being timed: "date"
	User time (seconds): 0.03
	System time (seconds): 0.00
	Percent of CPU this job got: 33%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.09
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 10396
	Average resident set size (kbytes): 0
	Major (requiring I/O) page faults: 2737
	Minor (reclaiming a frame) page faults: 0
	Voluntary context switches: 0
	Involuntary context switches: 0
	Swaps: 0
	File system inputs: 0
	File system outputs: 0
	Socket messages sent: 0
	Socket messages received: 0
	Signals delivered: 0
	Page size (bytes): 65536
	Exit status: 0

Some systems restrict ps(1) functionality partly because it does show
so much system information, and some environments like CygWin only
provide a subset of what the ps(1) command typically does on BSD and
GNU/Linux systems.

The timeit.f90 example program is a demonstration program for the
M_stopwatch module that on Posix
systems also gives User and System time but currently would just give
wallclock in most Microsoft environments, I think.

It does something similiar to your timer in just a few lines but to do
so uses several fpm(1) packages.

Fortran-based wallclock timer

Taking a lead from the timeit(1) program and your timer I put together
a stand-alone Fortran variant of the wallclock timer.

When I went to emulate your program in Fortran I admit it got longer than
I was initially thinking because of issues with requoting parameters. on
Unix/BSD/GNU-Linux it is relatively easy to requote the arguments but I am
not much of a Microsoft user so I am not sure if the same quoting rules
apply there or not. That aside I approximated your program in Fortran
using DATE_AND_TIME() and EXECUTE_COMMAND_LINE() extracting parts of a
few routines from M_time and
reducing the components from their general form to something just specific
to the task at hand.

Adding other Fortran intrinsics like CPU_TIME() would add some nice
additional functionality while still remaining relatively portable.

wallclock sleep 4
wallclock=4.146 sec or 0-00:00:04.146
wallclock.f90
program demo_wallclock
use,intrinsic :: iso_fortran_env, only : stderr=>ERROR_UNIT
implicit none
integer,parameter            :: dp=kind(0.0d0)
integer                      :: i,j,length,cmdstat,exitstat
character(len=*),parameter   :: doublequote='"',g0='(*(g0))',g1='(*(g0,1x))'
character(len=256)           :: cmdmsg
character(len=:),allocatable :: arg,arg_requote,cmd
real(kind=dp)                :: start,finish,wallclock
   cmd=''
   do i=1,command_argument_count()
      call get_command_argument(i,length=length)
      arg=repeat(' ',length)
      call get_command_argument(i,arg)
      arg_requote=''
      ! assume you double a quote on non-GNU/Linux and Unix to escape it
      ! probably needs changed in some PC environments
      do j=1,length
         arg_requote=arg_requote//arg(j:j)
         if(arg(j:j).eq.doublequote) arg_requote=arg_requote//doublequote
      enddo
      arg=arg_requote
      if(i.ne.1)arg=doublequote//arg//doublequote
      cmd=cmd//arg//' '
   enddo

   if(cmd.eq.'')stop

   start=now_as_julian()
   call execute_command_line(cmd,cmdstat=cmdstat,cmdmsg=cmdmsg,exitstat=exitstat)
   finish=now_as_julian()
   wallclock=(finish-start)*86400
   write(*,'("wallclock=",f0.3," sec or ",a,:," for ",a)')wallclock,sec2days(wallclock)!,cmd
   if(cmdstat.ne.0)then
      write(stderr,g0)trim(cmdmsg)
      stop 1
   endif
   if(exitstat.ne.0)then
      stop exitstat
   endif
contains
function now_as_julian() result(julian)
integer       :: dat(8), A, Y, M, JDN
real(kind=dp) :: julian
   call date_and_time(values=dat)
   associate(yr=>dat(1),mo=>dat(2),day=>dat(3),&
   & hr=>dat(5),min=>dat(6),sec=>dat(7)-(dat(4)*60)+dat(8)/1000.0_dp)
   A=(14-mo)/12
   Y=yr+4800-A
   M=mo+12*A-3
   JDN=day + (153*M+2)/5 + 365*Y + Y/4 - Y/100 + Y/400 - 32045
   julian=JDN + (hr-12)/24.0_dp + min/1440.0_dp + sec/86400.0_dp
end associate
end function now_as_julian

function sec2days(seconds) result(dhms)
use, intrinsic :: iso_fortran_env, only : int64
real(kind=dp),intent(in)     :: seconds
real(kind=dp)                :: remainder
character(len=40)            :: scratch
character(len=:),allocatable :: dhms
integer,parameter            :: one_day=86400, one_hour=3600, one_minute=60
integer(kind=int64)          :: days, hours, minutes, secs
integer                      :: i

   remainder=abs(seconds)

   days=remainder/one_day               
   remainder=remainder-days*one_day      

   hours=remainder/one_hour            
   remainder=remainder-hours*one_hour

   minutes=remainder/one_minute       
   remainder=remainder-minutes*one_minute

   secs=nint(remainder)
   remainder=remainder-secs

   write(scratch,'(i0,"-",i2.2,":",i2.2,":",i2.2,".",i3.3)') &
   &sign(days,merge(-1_int64,1_int64,seconds<0)),hours,minutes,secs,nint(remainder*1000)

   dhms=trim(scratch)
end function sec2days

end program demo_wallclock

FUNIX has a time-.f90 program

There are a number of Fortran sample programs that use the GPF(General Purpose Fortran) package that emulate or approximate GNU Linux commands in the funix section of apps. That includes time-.f90 which is a minature version of time(1).

1 Like

A new addition to my Fortran code list is

The author credits (Anthropic) Claude. A weakness of Fortran has been that the smaller number of developers compared to Python, C, or C++ means that there are fewer non-numerical utilities available. The many projects on GitHub ameliorate this, and now you can spin your own utility using an LLM.

1 Like