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.
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.
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.
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;
An alphabetically ordered synopsis for each of these functions now follows.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
/* 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.
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.