PDQ User Manual

PDQ User Manual

Updated: Dec 23, 2004

This online edition of the PDQ User Manual is intended to accompany the C language version of PDQ as presented in the book The Practical Performance Analyst and corrects several typos found in the original printing.

1  Contents

2  Overview

PDQ (Pretty Damn Quick) is a queueing model solver, not a simulator. The queueing theory models discussed in Chapters 2 and 3 of The Practical Performance Analyst. are incorporated into the solution methods used by PDQ. This saves you the labor of implementing thos codes each times you construct a queueing model and allows you to concentrate on the accuracy of your model rather than the accuracy of the solutions. PDQ guarantees that for you.

Because PDQ uses algorithmic techniques, rather than an event-based simulation (such as that used by C++SIM, NS, and REAL) it is extremely fast at calculating solutions. It is not too far fetched to say that, most often, PDQ takes more time to print out the result than it takes to calculate it!

Moreover, PDQ is a flexible library of functions rather than a hard-wired binary application. This flexibility requires that you express your performance model in the C language, and then compile it. Therefore, some programming is required. The good news is, you do not need to learn yet another strange modeling language. The bad news is, you have to learn the programming language called C.

In the spirit of the open-source development model (in fact, before it was even fashionable), PDQ is provided as multiple C source files to enable it to be compiled and used in the environment of your choice - from micros to mainframes.

This philosophy imposes two significant requirements.

2.1  System Requirements

You will need access to a C compiler e,g, gcc. Almost by definition, C compilers are available in every UNIX and Linux environment and C packages are now provided with most mainframes. There are also a great many C compiler products available for personal computer operating systems such Linux, DOS, Windows, and MacOS. For some readers (e.g., those with a mainframe background) this may appear as a burden. In fact, it greatly enhances the portability of your PDQ models.

2.2  Human Requirements

Since the source code provided with the book really constitutes a library of functions for solving performance models, all PDQ models must be written in C. PDQ works by linking its library routines into the C code for your performance model. This allows you to employ all the constructs of a modern procedural programming language such as: extensible data structures, block coding design, procedure calls, and recursion, to create and solve potentially very elaborate performance models.

All the examples in this book are constructed using this paradigm. Setting up a PDQ model is really quite straightforward. To help you gain familiarity with PDQ, you should first read the section on constructing a simple example in this manual. You should then read through a few of examples in the book (especially those in Chapters 2 and 3) to get a better idea of some of the variations on coding structure useful for creating and solving a PDQ model.

For those readers not yet familiar with the C language (or C++), PDQ offers another motivation to learn. The classic reference for ANSI standard C programming is authored by Kernighan and Ritchie. Many other excellent introductory texts on the C language are also available.

Remember, serious performance modeling is mostly serious programming.

2.3  The PDQ Library

In this section we describe the global variables, public data types, and public procedure calls available in the PDQ_Lib.h file.

2.3.1  Data Types

The following data types (implemented as #defines in the C language) are used in conjunction with PDQ library functions. See the synopses of procedures for actual syntax.

  1. PDQ Node Types

  2. Service Disciplines

  3. Workload Streams

  4. Solution Methods

2.3.2  Global Variables

The following global variables are mandatory for every PDQ model.

int nodes;
Cumulative counter for the number of nodes returned by PDQ_CreateNode

int streams;
Cumulative counter for the number of workload streams returned by PDQ_CreateClosed and PDQ_CreateOpen

The following global variables may also be useful in PDQ models.

int DEBUG;
Flag to toggle PDQ debug facility. Default is FALSE. Passed as an argument to PDQ_SetDebug

char model[MAXBUF];
Character array containing the model name. Initialized via PDQ_Init

MAXBUF = 50
double tolerance;
Controls the number of iterations used in the APPROXimate MVA solution method.

In addition, the following types (which are actually C structures) retain information about queueing nodes and workloads. See PDQ_Lib.h to see how to de-reference specific fields.

batch_type \*bt;
Contains the name and number in this batch class workload together with system statistics.

job_type \*job;
Describes the workload type either Terminal, Batch, or Transaction and the queueing circuit type, open or closed.

node_type \*node; Describes the queueing node attributes such as: queue length, utilization, and residence time.

systat_type \*sys;
Describes system attributes such as: throughput, and response time.

terminal_type \*tm;
Similar to batch_type but includes thinktime.

transaction_type \*tx; Contains the name of the open circuit workload together with system statistics.

For example, to determine the response time for the third stream of a transaction class workload, the appropriate C construct would be:
job[3].trans->sys->response;

2.3.3  Functions

All PDQ library functions have a PDQ_ prefix. The following list groups together the PDQ library procedures in the order of their typical invocation:

  1. PDQ_Init
  2. PD_CreateOpen or PDQ_CreateClosed
  3. PDQ_CreateNode
  4. PDQ_SetDemand or PDQ_SetVisits
  5. PDQ_SetWUnit and/or PDQ_SetTUnit
  6. PDQ_SetDebug
  7. PDQ_Solve
  8. PDQ_GetResponse and/or PDQ_GetThruput
  9. PDQ_GetThruMax
  10. PDQ_GetLoadOpt
  11. PDQ_GetUtilization and/or PDQ_GetResidenceTime and/or PDQ_GetQueueLength
  12. PDQ_Report

An alphabetically ordered synopsis for each of these functions now follows.

3  Function Synopses

NAME

PDQ_CreateClosed - define the workload for a closed circuit
queueing
circuit

SYNOPSIS

int PDQ_CreateClosed(char *name, int class, float pop, float
think);

DESCRIPTION

Used to define a workload for a closed circuit queueing model.  A
separate call is required for workload streams having different
characteristics.

OPTIONS

name: The string used to identify the workload in reports or
debug logs

class: Either TERM, or BATCH type

pop: The number of active user processes in the closed circuit.
This argument is a float to accommodate  measured activity e.g.,
57.4 average active users

think: The user delay or "thinktime" before a request re-enters
the queueing system}

RETURNS: the cumulative number of workload streams created.

EXAMPLE

main() {
   ...
   PDQ_CreateClosed("DB_workers", TERM, 57.4, 31.6);
   PDQ_CreateClosed("fax_tasks", BATCH, 10.0);
   ...
}

SEE ALSO

PDQ_CreateOpen, PDQ_Init


NAME

PDQ_CreateNode - define a queueing service center 

SYNOPSIS

int PDQ_CreateNode(char *name, int device, int sched);

DESCRIPTION

Defines a queueing service node for either a closed or
open circuit model.  A separate call is required for each
queueing node.

name: A string used to identify the service node in reports or
debug logs.

device: Type of device.  Typically CEN.

sched: The queueing discipline.  Most commonly FCFS.

RETURNS: the the cumulative number of queueing nodes created.

EXAMPLE

main() {
   ...
   PDQ_CreateNode("cpu", CEN, FCFS);
   PDQ_CreateNode("bus", CEN, ISRV);
   PDQ_CreateNode("disk", CEN, FCFS);
   ...
}

SEE ALSO

PDQ_CreateOpen, PDQ_Init


NAME

PDQ_CreateOpen - define an open circuit queueing workload

SYNOPSIS

int PDQ_CreateOpen(char *name, float lambda);

DESCRIPTION

Define a workload in an open circuit queueing model.  A separate
call is required for workload streams having different
characteristics.

name: A string used to identify the workload in reports or debug
logs.

lambda: The arrival rate per unit time into the queueing circuit.

RETURNS: the cumulative number of open workloads created.

EXAMPLE

main() {
   ...

   PDQ_CreateOpen("IO_Cmds", 10.0);

   ...
}

SEE ALSO

PDQ_CreateClosed, PDQ_Init


NAME

PDQ_GetLoadOpt - determine the optimal user load

SYNOPSIS

double PDQ_GetLoadOpt(int class, char *wname);

DESCRIPTION

PDQ_GetLoadOpt is used to determine the system throughput for the
specified workload.

class: TERM, or BATCH type.

wname: A string containing the name of the workload.

RETURNS: returns the optimal user load as a decimal number.

EXAMPLE

main() {

   double nopt;
   ...

   nopt = PDQ_GetLoadOpt(TRANS, "IO_Cmds");
   printf("Nopt(%s): %3.4f\n", "IO_Cmds", tp);

   ...
}

SEE ALSO

PDQ_GetThruput, PDQ_GetResponse


NAME

PDQ_GetQueueLength - determine the queue length at a particular device or node.

SYNOPSIS

double PDQ_GetQueueLength(char *device, char *work, int class);

DESCRIPTION

PDQ_GetQueueLength is used to determine the queue length of the
designated service node by the specified workload.  It should only
be called after the PDQ model has been solved.

device: A string containing the name of the queueing service node.

work: A string containing the name of the workload.

class: TRANS, TERM, or BATCH type.

RETURNS: returns the queue length as a decimal number.

EXAMPLE

main() {

   double ql;
   ...
   
   PDQ_Solve();
   ...

   ql = PDQ_GetQueueLength("disk", "IO_Cmds", TERM);
   printf("Q(%s): %3.4f\n","IO_Cmds", ql);
}

SEE ALSO

PDQ_GetResidenceTime, PDQ_GetUtilization


NAME

PDQ_GetResidenceTime - determine the residence time at a particular device or node.

SYNOPSIS

double PDQ_GetResidenceTime(char *device, char *work, int class);

DESCRIPTION

PDQ_GetResidenceTime is used to determine the residence time at the
designated service node by the specified workload.  It should only
be called after the PDQ model has been solved.

device: A string containing the name of the queueing service node.

work: A string containing the name of the workload.

class: TRANS, TERM, or BATCH type.

RETURNS: returns the residence time as a decimal number.

EXAMPLE

main() {

   double rez;
   ...
   
   PDQ_Solve();
   ...

   rez = PDQ_GetResidenceTime("disk", "IO_Cmds", TERM);
   printf("R(%s): %3.4f\n","IO_Cmds", rez);
}

SEE ALSO

PDQ_GetQueueLength, PDQ_GetUtilization


NAME

PDQ_GetResponse - get the system response time

SYNOPSIS

double PDQ_GetResponse(int class, char *wname);

DESCRIPTION

PDQ_GetResponse used to determine the system response time for the
specified workload.

class: TRANS, TERM, or BATCH type.

wname: A string containing the name of the workload.

RETURNS: the system response time as a decimal number.

EXAMPLE

main() {

   double rt;
   ...
   rt = PDQ_GetResponse(TRANS, "IO_Cmds");
   printf("R(%s): %3.4f\n","IO_Cmds", rt);

   ...
}

SEE ALSO

PDQ_CreateClosed, PDQ_Init, PDQ_CreateOpen


NAME

PDQ_GetThruMax - determine the system throughput at saturation

SYNOPSIS

double PDQ_GetThruMax(int class, char *wname);

DESCRIPTION

PDQ_GetThruMax is used to determine the system throughput for the
specified workload.

class: TERM, or BATCH type.

wname: A string containing the name of the workload.

RETURNS: returns the saturation system throughput as a decimal number.

EXAMPLE

main() {

   double xmax;
   ...

   xmax = PDQ_GetThruMax(TRANS, "IO_Cmds");
   printf("R(%s): %3.4f\n", "IO_Cmds", xmax);

   ...
}

SEE ALSO

PDQ_GetThruput


NAME

PDQ_GetThruput - determine the system throughput

SYNOPSIS

double PDQ_GetThruput(int class, char *wname);

DESCRIPTION

PDQ_GetThruput is used to determine the system throughput for the
specified workload.

class: TRANS, TERM, or BATCH type.

wname: A string containing the name of the workload.

RETURNS: returns the system throughput as a decimal number.

EXAMPLE

main() {

   double tp;
   ...

   tp = PDQ_GetThruput(TRANS, "IO_Cmds");
   printf("R(%s): %3.4f\n", "IO_Cmds", tp);

   ...
}

SEE ALSO

PDQ_GetResponse


NAME

PDQ_GetUtilization - determine the utilization of a particular device or node.

SYNOPSIS

double PDQ_GetUtilization(char *device, char *work, int class);

DESCRIPTION

PDQ_GetUtilization is used to determine the utilization of the
designated service node by the specified workload.  It should only
be called after the PDQ model has been solved.

device: A string containing the name of the queueing service node.

work: A string containing the name of the workload.

class: TRANS, TERM, or BATCH type.

RETURNS: returns the utilization as a decimal fraction in the range 0.0 to 1.0.

EXAMPLE

main() {

   double ut;
   ...
   
   PDQ_Solve();
   ...

   ut = PDQ_GetUtilization("disk", "IO_Cmds", TERM);
   printf("R(%s): %3.4f\n","IO_Cmds", ut);
}

SEE ALSO

PDQ_GetResponse, PDQ_GetThruput, PDQ_Solve


NAME

PDQ_Init - initializes all internal PDQ variables

SYNOPSIS

void PDQ_Init(char *name);

DESCRIPTION

Initializes all internal PDQ variables.  Must be called prior to
any other PDQ function. It also resets all internal PDQ variables
so that no separate cleanup function call required.  PDQ_Init can
be called an arbitrary number of times in the same model.

name: A string containing the name of the performance model that
will appear in the PDQ report banner.  To maintain cosmetic
appearances, the model name should not exceed 24 characters
(including spaces).

RETURNS: None.

EXAMPLE

main() {
   
   PDQ_Init("File Server");
   ...

   PDQ_Solve();
   PDQ_Report();

   PDQ_Init("Client Workstation");
   ...

   PDQ_Solve();
   PDQ_Report();
}

SEE ALSO

PDQ_Solve, PDQ_Report


NAME

PDQ_Report - generates a formatted report

SYNOPSIS

void PDQ_Report();

DESCRIPTION

PDQ_Report generates a formatted report that includes the total
number of nodes and workloads created in the model, system level
performance measures such as throughput and response time for each
workload, and service node performance measures such as node
utilization and queue lengths. A comment field is available to
audit input parameter variations across multiple runs of the same
model.  To add comments to a report, simply create or modify a
file named comments.pdq. Sample reports produced by PDQ Reporter
appear throughput this book.

RETURNS: None.

EXAMPLE

main() {
   ...

   PDQ_Solve();
   PDQ_Report();
}

SEE ALSO

PDQ_Init, PDQ_Solve


NAME

PDQ_SetDebug - enable diagnostic printout

SYNOPSIS

void PDQ_SetDebug(int flag);

DESCRIPTION

Enables diagnostic printout of internal variables and procedures
used in solving a PDQ model.

flag: Set either TRUE or FALSE to toggle the debug facility.

RETURNS: None.  Output is written to file e.g., debug.log.

EXAMPLE

main() {
   ...
   PDQ_SetDebug(TRUE);
   nodes = PDQ_CreateNode("server", CEN, FCFS); 
   streams = PDQ_CreateOpen("work", 0.5); 
   PDQ_SetDemand("server", "work", 1.0);
   ...
   PDQ_SetDebug(FALSE);
}

Produces the following output.

DEBUG: PDQ_CreateNode
        Entering
        Node[0]: CEN FCFS "server"
        Exiting
        Stream[0]:  TRANS       "work"; Lambda: 0.5
DEBUG: PDQ_SetDemand()
        Entering
DEBUG: getnode_index()
        Entering
        node:"server"  index: 0
        Exiting
        ...

SEE ALSO

PDQ_Init


NAME

PDQ_SetDemand - define the service demand (or service time)

SYNOPSIS

void PDQ_SetDemand(char *nodename, char *workname, float time);

DESCRIPTION

Define the service demand of a specific
workload.  The named node and workload must have been defined
previously. A separate call is required for each workload stream that
accesses the same node.

nodename: the string name of the queueing node.

workname: the string name of the workload.

time: service demand (in units of time) required by the workload
at that node.

RETURNS: None.

EXAMPLE

main() {
   ...
   PDQ_CreateClosed("DB_Workers", TERM, 57.4, 31.6);
   PDQ_CreateClosed("Fax_Report", BATCH, 10.0);
   PDQ_CreateNode("cpu", CEN, FCFS);
   PDQ_SetDemand("cpu", "DB_Workers", 0.130);
   PDQ_SetDemand("cpu", "Fax_Report", 3.122);
   ...
}

SEE ALSO

PDQ_CreateClosed, PDQ_CreateNode, PDQ_CreateOpen, PDQ_SetVisits 


NAME

PDQ_SetTUnit - change the timebase unit

SYNOPSIS

void PDQ_SetTUnit(char *unitname);

DESCRIPTION

Change the name of the time unit that appears in the PDQ
report.   The default time unit is Seconds.

unitname: a string name of the unit

RETURNS: None.

EXAMPLE

main() {
   ...
   PDQ_SetTUnit("Minutes");
   ...
 }

SEE ALSO

PDQ_Report


NAME

PDQ_SetVisits - define the visit count

SYNOPSIS

void PDQ_SetVisits(char *nodename, char *workname, 
                   float visits, float service);

DESCRIPTION

Used to define the service demand of a specific workload in terms
of the explicit service time and visit count.  The named node and
workload must exist. A separate call is required for each workload
stream that accesses the same node.  PDQ_SetVisits is different
from PDQ_SetDemand in the way node-level performance metrics are
formatted in the PDQ_Report output. The number of visits shows up
in the PDQ_Report INPUTS section. The throughput in the RESOURCE
Performance section shows up as counts per unit time.

nodename: name of the queueing node.

workname: name of the workload.

visits: number of visits to that node. Dimensionless.

service: service time the workload requires at that node (in time
units).

RETURNS: None.

EXAMPLE

main() {
   ...
   PDQ_CreateClosed("DB_Workers", TERM, 57.4, 31.6);
   PDQ_CreateNode("cpu", CEN, FCFS);
   PDQ_SetVisits("cpu", "DB_Workers", 10.0, 0.013);
   ...
}

SEE ALSO

PDQ_CreateClosed, PDQ_CreateNode, PDQ_CreateOpen, PDQ_SetDemand


NAME

PDQ_SetWUnit - change the name of the workload unit

SYNOPSIS

void PDQ_SetWUnit(char *unitname);

DESCRIPTION

PDQ_SetWUnit changes the name of the work unit that appears in the
PDQ report. The default work unit is Job.

unitname: The name of the work unit.

RETURNS: None.

EXAMPLE

main() {
   ...
   PDQ_SetWUnit("I/O_Reqs");
   ...
 }

SEE ALSO

PDQ_Report


NAME

PDQ_Solve - solve the PDQ model

SYNOPSIS

int PDQ_Solve(int method);

DESCRIPTION

PDQ_Solve is called after the PDQ model has been created.   An
appropriate solution method must be passed as an argument or an
error will reported at runtime.

method: APPROX or CANON.
Note: The EXACT method is highly complex and has not been included
in this version of the PDQ library. See Chapter 3 for more
details.

RETURNS: None.

EXAMPLE

main() {
   ...
   PDQ_Solve(APPROX);
   PDQ_Report();
}

SEE ALSO

PDQ_Report

4  Simple Example

The following annotated C source code is intended to provide a simple boiler-plate for creating a PDQ performance model. The entire model is defined, and solved within the main function. In more sophisticated models, it may be preferable to write separate functions to handle different aspects of the model.

4.1  Creating the Model

It is a good idea to start the PDQ model off with a unique filename and a description of its purpose. All this goes in a C comment field commencing with /* and ending with */.

/*  min_mod.c
	
	Illustrate use of PDQ using a single open workload server.
*/

The filename for our model is min_mod.c.

It is good practice to include the following header files in any PDQ model source code.

#include <stdio.h>
#include <math.h>
#include "PDQ_Lib.h"

main() {

It is necessary to declare the PDQ global variables: nodes and streams. They enable other internal PDQ procedures to keep track of the global state of the queueing circuit created in PDQ.

	extern int	nodes, streams;

Variables that are specific to the model are declared next. In this case, we declare the interarrival time and the service time for the workload.

	float inter_arriv_time = 0.5;
	float service_time = 1.0;

The model is initialized by a call to

PDQ_Init 

with the name of the model that will appear in the PDQ report.

The actual PDQ model name that will appear in the PDQ report is Minimal Model and need not be the same as the PDQ filename, although it is a good idea to keep them as similar as possible. We also assign time and work units that are different from the defaults.

  	PDQ_Init("Minimal Model"); 
  	PDQ_SetWUnits("Compltns"); 
  	PDQ_SetTUnits("Time"); 

The next step is define the queueing centers or nodes that comprise the queueing circuit and the workload that will place service demands on those nodes.

    nodes = PDQ_CreateNode("server", CEN, FCFS); 

There is only one node and its name is server. We create an open circuit model by calling

    PDQ_CreateOpen 

and passing the workload name work and its interarrival time. This model is an open circuit type.

    streams = PDQ_CreateOpen("work", inter_arriv_time); 

The service demand placed in the node server by the workload work is defined in the call to

    PDQ_SetDemand.

    PDQ_SetDemand("server", "work", service_time);

At this point, the PDQ model is now defined and created in memory. All that remains is to solve it and examine the performance data predicted by the PDQ model. Since this is an open circuit model, the appropriate solution technique is the canonical method.

    PDQ_Solve(CANON);
    
    PDQ_Report();
      
} /* main */

The report generated by the call to

	PDQ_Report 

is annotated in the next section. The C source code for more elaborate PDQ models appear throughout the book.

4.2  PDQ Report

This is an annotated version of the report generated for the Minimal Model described in the previous section. The banner includes the PDQ model name, Minimal Model, that was passed as an argument to
	PDQ_Init.
The banner is followed by the comment field (if any has been supplied).


			***************************************
			****** Pretty Damn Quick REPORT *******
			***************************************
			***  of : Tue Apr  2 19:56:07 1996  ***
			***  for: Minimal Model             ***
			***  Ver: PDQ Analyzer v1.0         ***
			***************************************
			***************************************

			******        Comments          *******

I couldn't think of anything to say.

The following section of the report headed: PDQ Model INPUTS, summarizes the number and types of nodes and workloads. In this case there is only one node and one workload in an open circuit model. The service demand placed on the node by the workload is 1 Second.

			***************************************
			******    PDQ Model INPUTS      *******
			***************************************

Node Sched Resource   Workload   Class     Demand
---- ----- --------   --------   -----     ------
CEN  FCFS  server     work       TRANS     1.0000

Queueing Circuit Totals:
        Generators: 0.00
        Streams   :   1
        Nodes     :   1

There are no generators because this an open queueing circuit, not a closed circuit with a finite population generating work. Next, the workload and their demands are summarized.

WORKLOAD Parameters

Arrivals     per Sec       Demand
--------     -------       ------
work         0.5000        1.0000

The next section , entitled: PDQ Model OUTPUTS, summarizes both system level and node level performance measures.

			***************************************
			******   PDQ Model OUTPUTS      *******
			***************************************

Solution Method: CANON

The PDQ report reminds us which of the solution techniques was used in solving the model. In this case, it's the canonical solution.

Each workload component is identified and the system level performance measures are reported that workload component. In the case of our Minimal Model, there is only one component which was named "work." There is no bounds analysis performed since this is an open circuit model.

		******   SYSTEM Performance     *******

Metric                       Value      Unit      
-----------------            -----      ----      
Workload: "work"
Mean Throughput             0.5000      Compltns/Time
Response Time               2.0000      Time
Bounds Analysis:
Max Throughput              1.0000      Compltns/Time

Finally, node level attribites such as, node utilization and queue length at the node are reported. In the case of our Minimal Model, there is only one node which was named ßerver" and it has service demands placed on it by the single workload component called work.

		******   RESOURCE Performance   *******

Metric          Resource	Work	Value  	Unit   
---------       --------	----	-----  	----   
Throughput      server		work	0.5000	Compltns/Time
Utilization     server		work	50.0000	Percent
Queue Length    server		work	1.0000	Compltns
Residence Time  server		work	2.0000	Time

This is the end of the PDQ Report.

5  Summary

All the examples used throughout the book, are constructed using the PDQ library described in this manual. After reading how to create the Minimal Model discussed in this manual, you should turn some of the examples in the text, especially those in Chapters 2 and 3, to get a better idea of how to apply PDQ to the creation and solution of queueing performance model.


File translated from TEX by TTH, version 2.25.
On 23 Dec 2004, 09:24.