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