char   netcpu_ntperf_id[]="\
@(#)netcpu_ntperf.c (c) Copyright 2005, Hewlett-Packard Company, Version 2.4.0";

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>

#if HAVE_INTTYPES_H
# include <inttypes.h>
#else
# if HAVE_STDINT_H
#  include <stdint.h>
# endif
#endif

#include "netsh.h"
#include "netlib.h"

//
// System CPU time information class.
// Used to get CPU time information.
//
//     SDK\inc\ntexapi.h 
// Function x8:   SystemProcessorPerformanceInformation
// DataStructure: SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION 
//

#define SystemProcessorPerformanceInformation 0x08

typedef struct 
{
        LARGE_INTEGER   IdleTime;
        LARGE_INTEGER   KernelTime;
        LARGE_INTEGER   UserTime;
        LARGE_INTEGER   DpcTime;
        LARGE_INTEGER   InterruptTime;
        LONG                    InterruptCount;
} SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION, *PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION;

//
// Calls to get the information
//
typedef ULONG (__stdcall *NT_QUERY_SYSTEM_INFORMATION)( 
                                                                                        ULONG SystemInformationClass, 
                                                                                        PVOID SystemInformation, 
                                                                                        ULONG SystemInformationLength,
                                                                                        PULONG ReturnLength 
                                                                                        );

NT_QUERY_SYSTEM_INFORMATION NtQuerySystemInformation = NULL;


static LARGE_INTEGER TickHz;


/* The NT performance data is accessed through the NtQuerySystemInformation
   call.  References to the PDH.DLL have been deleted.  This structure
   is the root for these data structures. */

typedef struct sPerfObj 
{
        LARGE_INTEGER   StartTime;
        LARGE_INTEGER   EndTime;
        SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION StartInfo[MAXCPUS +1];
        SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION EndInfo[MAXCPUS +1];
} PerfObj, *PPerfObj;

static PerfObj *PerfCntrs;

// Forward declarations

PerfObj *InitPerfCntrs();
void RestartPerfCntrs(PerfObj *PerfCntrs);
double ReportPerfCntrs(PerfObj *PerfCntrs);  /* returns CPU utilization */
void ClosePerfCntrs(PerfObj *PerfCntrs);


void
cpu_util_init(void) 
{
  if (NtQuerySystemInformation == NULL) {
    // Open the performance counter interface
    PerfCntrs = InitPerfCntrs();
  }
  return;
}

void
cpu_util_terminate(void)
{
  return;
}

int
get_cpu_method(void)
{
  return NT_METHOD;
}

void
get_cpu_idle(uint64_t *res)
{
  RestartPerfCntrs(PerfCntrs);
  return;
}

float
calibrate_idle_rate(int iterations, int interval)
{
  return;
}


/*
  InitPerfCntrs() - 

  Changed to no longer access the NT performance registry interfaces.
  A direct call to NtQuerySystemInformation (an undocumented NT API)
  is made instead.  Parameters determined by decompilation of ntkrnlmp
  and ntdll.
*/


PerfObj *InitPerfCntrs()
{
  PerfObj *NewPerfCntrs;
  DWORD NTVersion;
  DWORD status;
  
  NewPerfCntrs = (PerfObj *)GlobalAlloc(GPTR, sizeof(PerfObj));
  assert(NewPerfCntrs != NULL);
  
  ZeroMemory((PCHAR)NewPerfCntrs, sizeof(PerfObj));
  
  // get NT version
  NTVersion = GetVersion();
  if (NTVersion >= 0x80000000) 
    {
      fprintf(stderr, "Not running on Windows NT\n");
      exit(1);
    }
  
  // locate the calls we need in NTDLL
  //Lint 
  NtQuerySystemInformation = 
    (NT_QUERY_SYSTEM_INFORMATION)GetProcAddress( GetModuleHandle("ntdll.dll"),
						 "NtQuerySystemInformation" );

  if ( !(NtQuerySystemInformation) )
    {
      //Lint
      status = GetLastError();
      fprintf(stderr, "GetProcAddressFailed, status: %X\n", status);
      exit(1);
    }
  
  // setup to measure timestamps with the high resolution timers.
  if (QueryPerformanceFrequency(&TickHz) == FALSE)
    {
      fprintf(stderr,"MAIN - QueryPerformanceFrequency Failed!\n");
      exit(2);   
    }
  
  RestartPerfCntrs(NewPerfCntrs);
  
  return(NewPerfCntrs);
}  /* InitPerfCntrs */

/*
  RestartPerfCntrs() -

  The Performance counters must be read twice to produce rate and
  percentage results.  This routine is called before the start of a
  benchmark to establish the initial counters.  It must be called a
  second time after the benchmark completes to collect the final state
  of the performance counters.  ReportPerfCntrs is called to print the
  results after the benchmark completes.
*/

void RestartPerfCntrs(PerfObj *PerfCntrs)
{
  DWORD returnLength = 0;  //Lint
  DWORD returnNumCPUs;  //Lint
  DWORD i;
  
  DWORD status;
  
  // Move previous data from EndInfo to StartInfo.
  CopyMemory((PCHAR)&PerfCntrs->StartInfo[0],
	     (PCHAR)&PerfCntrs->EndInfo[0],
	     sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)*(MAXCPUS +1));
  
  PerfCntrs->StartTime = PerfCntrs->EndTime;
  
  // get the current CPUTIME information
  if ( (status = NtQuerySystemInformation( SystemProcessorPerformanceInformation,
					   (PCHAR)&PerfCntrs->EndInfo[0], sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)*MAXCPUS,
					   &returnLength )) != 0) 
    {
      fprintf(stderr, "NtQuery failed, status: %X\n", status);
      exit(1);
    }
  
  PerfCntrs->EndTime = ReadPerformanceCounter();
  
  // Validate that NtQuery returned a reasonable amount of data
  if ((returnLength % sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)) != 0)
    {
      fprintf(stderr, "NtQuery didn't return expected amount of data\n");
      fprintf(stderr, "Expected a multiple of %i, returned %i\n",
	      sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), returnLength);
      exit(1);
    }
  returnNumCPUs = returnLength / sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION); 
  
  if (returnNumCPUs != (int)SystemInfo.dwNumberOfProcessors)
    {
      fprintf(stderr, "NtQuery didn't return expected amount of data\n");
      fprintf(stderr, "Expected data for %i CPUs, returned %i\n",
	      (int)SystemInfo.dwNumberOfProcessors, returnNumCPUs);
      exit(1);
    }
  
  // Zero entries not returned by NtQuery
  ZeroMemory((PCHAR)&PerfCntrs->EndInfo[returnNumCPUs],
	     sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)*
	     (MAXCPUS +1 - returnNumCPUs));
  
  // Total all of the CPUs
  //      KernelTime needs to be fixed-up; it includes both idle &
  // true kernel time 
  //  Note that kernel time also includes DpcTime & InterruptTime, but
  // I like this. 
  for (i=0; i < returnNumCPUs; i++)
    {
      PerfCntrs->EndInfo[i].KernelTime.QuadPart         -= PerfCntrs->EndInfo[i].IdleTime.QuadPart;
      PerfCntrs->EndInfo[MAXCPUS].IdleTime.QuadPart     += PerfCntrs->EndInfo[i].IdleTime.QuadPart;
      PerfCntrs->EndInfo[MAXCPUS].KernelTime.QuadPart   += PerfCntrs->EndInfo[i].KernelTime.QuadPart;
      PerfCntrs->EndInfo[MAXCPUS].UserTime.QuadPart     += PerfCntrs->EndInfo[i].UserTime.QuadPart;
      PerfCntrs->EndInfo[MAXCPUS].DpcTime.QuadPart      += PerfCntrs->EndInfo[i].DpcTime.QuadPart;
      PerfCntrs->EndInfo[MAXCPUS].InterruptTime.QuadPart += PerfCntrs->EndInfo[i].InterruptTime.QuadPart;
      PerfCntrs->EndInfo[MAXCPUS].InterruptCount                += PerfCntrs->EndInfo[i].InterruptCount;
    }
  
}   /* RestartPerfCntrs */

/*
  ReportPerfCntrs() -
  This routine reports the results of the various performance
  counters. 
*/

double ReportPerfCntrs(PerfObj *PerfCntrs)
{
  double tot_CPU_Util;
  int i;
  int duration;  // in 100 usecs
  
  LARGE_INTEGER ActualDuration;
  
  SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION        DeltaInfo[MAXCPUS +1];  
  
  LARGE_INTEGER   TotalCPUTime[MAXCPUS +1];         
  
  for (i=0; i <= MAXCPUS; i++)
    {
      DeltaInfo[i].IdleTime.QuadPart    = PerfCntrs->EndInfo[i].IdleTime.QuadPart -
	PerfCntrs->StartInfo[i].IdleTime.QuadPart;
      DeltaInfo[i].KernelTime.QuadPart          = PerfCntrs->EndInfo[i].KernelTime.QuadPart -
	PerfCntrs->StartInfo[i].KernelTime.QuadPart;
      DeltaInfo[i].UserTime.QuadPart    = PerfCntrs->EndInfo[i].UserTime.QuadPart -
	PerfCntrs->StartInfo[i].UserTime.QuadPart;
      DeltaInfo[i].DpcTime.QuadPart     = PerfCntrs->EndInfo[i].DpcTime.QuadPart -
	PerfCntrs->StartInfo[i].DpcTime.QuadPart;
      DeltaInfo[i].InterruptTime.QuadPart = PerfCntrs->EndInfo[i].InterruptTime.QuadPart -
	PerfCntrs->StartInfo[i].InterruptTime.QuadPart;
      DeltaInfo[i].InterruptCount               = PerfCntrs->EndInfo[i].InterruptCount -
	PerfCntrs->StartInfo[i].InterruptCount;
      
      TotalCPUTime[i].QuadPart =      
	DeltaInfo[i].IdleTime.QuadPart +
	DeltaInfo[i].KernelTime.QuadPart +
	DeltaInfo[i].UserTime.QuadPart;
      // KernelTime already includes DpcTime & InterruptTime!
      // + DeltaInfo[i].DpcTime.QuadPart  +
      //  DeltaInfo[i].InterruptTime.QuadPart;
    }
  
  tot_CPU_Util = 100.0*(1.0 - (double)DeltaInfo[MAXCPUS].IdleTime.QuadPart/(double)TotalCPUTime[MAXCPUS].QuadPart);  //Lint
  
  // Re-calculate duration, since we may have stoped early due to cntr-C.
  ActualDuration.QuadPart = PerfCntrs->EndTime.QuadPart - 
    PerfCntrs->StartTime.QuadPart;
  
  // convert to 1/10 milliseconds (100 usec) 
  ActualDuration.QuadPart = (ActualDuration.QuadPart*10000)/TickHz.QuadPart;
  duration = ActualDuration.LowPart;
  
  if (verbosity > 1)
    {
      fprintf(where,"ActualDuation (ms): %d\n", duration/10);
    }
  
  if (verbosity > 1)
    {
      fprintf(where, "%% CPU    _Total");
      if ((int)SystemInfo.dwNumberOfProcessors > 1)
	{
	  for (i=0; i < (int)SystemInfo.dwNumberOfProcessors; i++)
	    {
	      fprintf(where, "\t CPU %i", i);
	    }
	}
      fprintf(where, "\n");
      
      fprintf(where, "Busy      %5.2f", tot_CPU_Util);
      if ((int)SystemInfo.dwNumberOfProcessors > 1)
	{
	  for (i=0; i < (int)SystemInfo.dwNumberOfProcessors; i++)
	    {
	      fprintf(where, "\t %5.2f", 
		      100.0*(1.0 - (double)DeltaInfo[i].IdleTime.QuadPart/(double)TotalCPUTime[i].QuadPart));  //Lint
	    }
	}
      fprintf(where, "\n");
      
      fprintf(where, "Kernel    %5.2f", 
	      100.0*(double)DeltaInfo[MAXCPUS].KernelTime.QuadPart/(double)TotalCPUTime[MAXCPUS].QuadPart);  //Lint
      
      if ((int)SystemInfo.dwNumberOfProcessors > 1)
	{
	  for (i=0; i < (int)SystemInfo.dwNumberOfProcessors; i++)
	    {
	      fprintf(where, "\t %5.2f", 
		      100.0*(double)DeltaInfo[i].KernelTime.QuadPart/(double)TotalCPUTime[i].QuadPart);  //Lint
	    }
	}
      fprintf(where, "\n");
      
      fprintf(where, "User      %5.2f", 
	      100.0*(double)DeltaInfo[MAXCPUS].UserTime.QuadPart/(double)TotalCPUTime[MAXCPUS].QuadPart);
      
      if ((int)SystemInfo.dwNumberOfProcessors > 1)
	{
	  for (i=0; i < (int)SystemInfo.dwNumberOfProcessors; i++)
	    {
	      fprintf(where, "\t %5.2f", 
		      100.0*(double)DeltaInfo[i].UserTime.QuadPart/TotalCPUTime[i].QuadPart);  //Lint
	    }
	}
      fprintf(where, "\n");
      
      fprintf(where, "Dpc       %5.2f", 
	      100.0*(double)DeltaInfo[MAXCPUS].DpcTime.QuadPart/(double)TotalCPUTime[MAXCPUS].QuadPart);  //Lint
      
      if ((int)SystemInfo.dwNumberOfProcessors > 1)
	{
	  for (i=0; i < (int)SystemInfo.dwNumberOfProcessors; i++)
	    {
	      fprintf(where, "\t %5.2f", 
		      100.0*(double)DeltaInfo[i].DpcTime.QuadPart/(double)TotalCPUTime[i].QuadPart);  //Lint
	    }
	}
      fprintf(where, "\n");
      
      fprintf(where, "Interrupt %5.2f", 
	      100.0*(double)DeltaInfo[MAXCPUS].InterruptTime.QuadPart/(double)TotalCPUTime[MAXCPUS].QuadPart);  //Lint
      
      if ((int)SystemInfo.dwNumberOfProcessors > 1)
	{
	  for (i=0; i < (int)SystemInfo.dwNumberOfProcessors; i++)
	    {
	      fprintf(where, "\t %5.2f", 
		      100.0*(double)DeltaInfo[i].InterruptTime.QuadPart/TotalCPUTime[i].QuadPart);  //Lint
	    }
	}
      fprintf(where, "\n\n");
      
      fprintf(where, "Interrupt/Sec. %5.1f", 
	      (double)DeltaInfo[MAXCPUS].InterruptCount*10000.0/(double)duration);
      
      if ((int)SystemInfo.dwNumberOfProcessors > 1)
	{
	  for (i=0; i < (int)SystemInfo.dwNumberOfProcessors; i++)
	    {
	      fprintf(where, "\t %5.1f", 
		      (double)DeltaInfo[i].InterruptCount*10000.0/(double)duration);
	    }
	}
      fprintf(where, "\n\n");
      fflush(where);
    }
  
  return (tot_CPU_Util);
  
}  /* ReportPerfCntrs */

/*
  ClosePerfCntrs() -  

  This routine cleans up the performance counter APIs.
*/

void ClosePerfCntrs(PerfObj *PerfCntrs)
{
        GlobalFree(PerfCntrs);

        NtQuerySystemInformation = NULL;
}   /* ClosePerfCntrs */

void
cpu_start_internal(void)
{
}

void
cpu_stop_internal(void)
{
}

float
calc_cpu_util_internal(float elapsed_time) 
{
  float correction_factor;
  lib_local_cpu_util = (float)0.0;
  /* It is possible that the library measured a time other than */
  /* the one that the user want for the cpu utilization */
  /* calculations - for example, tests that were ended by */
  /* watchdog timers such as the udp stream test. We let these */
  /* tests tell up what the elapsed time should be. */
  
  if (elapsed_time != 0.0) {
    correction_factor = (float) 1.0 + 
      ((lib_elapsed - elapsed_time) / elapsed_time);
  }
  else {
    correction_factor = (float) 1.0;
  }

  if (debug) {
    fprintf(where, "correction factor: %f\n", correction_factor);
  }

  lib_local_cpu_util = (float)ReportPerfCntrs(PerfCntrs);
  lib_local_cpu_util *= correction_factor;
  return lib_local_cpu_util;

}
