Upper Bound calculation with multiple works treams in PyDQ

90 views
Skip to first unread message

James

unread,
Feb 21, 2011, 4:20:58 PM2/21/11
to Guerrilla Capacity Planning
I have some PyDQ code where something interesting is going on.

Below is my code and below that I will outline the issue that I am
seeing and my logic behind my thoughts.

Code:

8<------------------------
#!/usr/bin/python
import pdq

pdq.Init("arrivalRate = 38.4")

# Define all of the queues

pdq.CreateNode("cpu", pdq.CEN, pdq.FCFS)
pdq.CreateNode("nic.sent", pdq.CEN, pdq.FCFS)
pdq.CreateNode("nic.recv", pdq.CEN, pdq.FCFS)
pdq.CreateNode("dat.cpu", pdq.CEN, pdq.FCFS)

# Create workStreamA and set the related service demands

pdq.streams = pdq.CreateOpen("workStream_A", 3.84)

pdq.SetDemand("cpu", "workStream_A", 0.029)
pdq.SetDemand("nic.sent", "workStream_A", 0.01296)
pdq.SetDemand("nic.recv", "workStream_A", 0.00006)
pdq.SetDemand("dat.cpu", "workStream_A", 0.001379)

# Create workStreamB and set the related service demands

pdq.streams = pdq.CreateOpen("workStream_B", 34.56)

pdq.SetDemand("cpu", "workStream_B", 0.00725)
pdq.SetDemand("nic.sent", "workStream_B", 0.00324)
pdq.SetDemand("nic.recv", "workStream_B", 0.00006)
pdq.SetDemand("dat.cpu", "workStream_B", 0.000982)

pdq.Solve(pdq.CANON)
pdq.Report()

------------------------->8

I have defined two work streams, workStream_A and workStream_B.

I set the lambda for workStream_A to be 3.84 requests per second and
the lambda for workStream_B to be 34.56 requests per second.

I have four common queues between the two work streams but for right
now I will reference the "cpu" node as it is the one where I am having
an issue.

When I try to execute my code I get the following output:

8<---------------
ERROR in procedure 'canonical()':
Arrival rate 34.560 exceeds saturation throughput 34.483 = 1/0.029
---------------->8

This really makes me scratch my 'noggin.

I don't argue that the inverse of 0.029 is 34.483. But, what confuses
me is that I've set the demand for the CPU to be 0.029 for
workStream_A using the command:

pdq.SetDemand("cpu", "workStream_A", 0.029)

I have also set the lambda for workStream_A via the command:

pdq.streams = pdq.CreateOpen("workStream_A", 3.84)

The lambda for workStream_A at 3.84 requests per second is less than
12% than the 34.483 requests per second upper limit for a service
demand of 0.029 seconds for the CPU.

The error reads: "Arrival rate 34.560 exceeds saturation throughput
34.483 = 1/0.029"

My first thought is that lambda for workStream_B is being used to
check the for the upper limit from the service demand set for
workStream_A.

When I crunch the numbers by hand I get the following:

workStream_A rate = 3.84 requests per second
workStream_B rate = 34.56 requests per second

s_cpu_workStream_A = 0.029 seconds
s_cpu_workStream_B = 0.00725 seconds

rho_workStream_A = 3.894 requests per second * 0.029 seconds =>
0.11136
rho_workStream_B = 34.56 requests per second * 0.00725 seconds =>
0.25056

sum total rho for CPU is 0.11136 + 0.25056 for a total of 0.36192.

I enabled debugging output via "pdq.SetDebug(1)" and checking the
output I see the following output:

8<-----------------------
DEBUG: solve()
Entering
DEBUG: canonical()
Entering
Tot Util: 0.3619 for cpu
Tot Util: 0.1617 for nic.sent
Tot Util: 0.0023 for nic.recv
Tot Util: 0.0392 for dat.cpu
DEBUG: getjob_name()
Entering
Exiting
X[workStream_A]: 3.8400
R[workStream_A]: 0.0624
N[workStream_A]: 0.2396

------------------------>8

Notice that the my calculation for total utilization of the CPU node
agrees with the PDQ debug output. The output stops after
"N[workStream_A]: 0.2396" and the stopping error is dumped to STDERR.

I believe that I am setting up the PDQ model correctly but wanted
somebody else to check over my shoulder for me.

Thanks.

James

James

unread,
Feb 22, 2011, 12:29:53 PM2/22/11
to Guerrilla Capacity Planning
I was looking at the PDQ C source for the function that is throwing
the error that I am encountering and did a code walk-through (I don't
have a C compiler setup on my work machine so I haven't done an actual
debug session).

In MVA_Cannon.c there is a for loop that identifies the greatest
service demand in all the nodes created:



8<--------------------------
// find the bottleneck node (i.e., largest service demand)
for (k = 0; k < nodes; k++) {
// Hope to remove single class restriction
if (node[k].sched == MSQ && streams > 1) {
sprintf(s1, "Only single PDQ stream allowed with MSQ nodes.");
errmsg(p, s1);
}
Ddev = node[k].demand[c];
if (node[k].sched == MSQ) { // multiserver case
m = node[k].devtype; // contains number of servers > 0
Ddev /= m;
}
if (Ddev > Dsat) {
Dsat = Ddev;
}
}
-------------------------->8

This loop identifies the largest service demand from all the nodes
created. In my case above, the greatest demand, Dsat, is set to 0.029
seconds.

Later on in the code there is a check on the saturation rate:

8<---------------
job[c].trans->saturation_rate = Xsat = 1.0 / Dsat;

if (X > job[c].trans->saturation_rate) {
sprintf(s1,
"\nArrival rate %3.3f exceeds saturation throughput
%3.3f = 1/%3.3f",
X, Xsat, Dsat);
errmsg(p, s1);
}
---------------->8

The value for X in the above snippet is set on line 59 of MVA_Cannon.c
by `X = job[c].trans->arrival_rate;`

So, in my case, the Dsat is set to 0.029 and when the loop that does
the gonkulations comes around to my workStream_B with an arrival rate
of 34.56 per second, the saturation rate is less than the value for X
seen above.

X is set to 34.56 and the job[c].trans->saturation_rate is computed to
be 34.48 (1/0.029) so X is greater than saturation_rate and the error
is encountered.

However, this is a premature error as workLoad_A is not exceeding the
saturation rate and neither is workLoad_B.

Would it be better for the maximum service demand be gonkulated by the
current job being evaluated as opposed to the highest service demand
in all the nodes and then error out if node utilization exceeds 1.0?

Thanks.

James



DrQ

unread,
Feb 22, 2011, 1:30:26 PM2/22/11
to Guerrilla Capacity Planning
Interesting. I'll take a look just as soon as I can find some time.

--njg

James

unread,
Feb 22, 2011, 2:19:00 PM2/22/11
to Guerrilla Capacity Planning
After eating some tasty, tasty BBQ I was looking the code over again
and noticed that on line 69 the demand for the current job is being
looked at and not for all nodes:

8<-------------
Ddev = node[k].demand[c];
-------------->8

That means my prior statement is incorrect. The loop is looking at
only the current job. However, the value for Dsat is not set back to
zero with each loop working on the current job. So, when I setup my
model, I set up the heaviest demand for the lowest lamba for
workLoad_A. In the loop in the message above, once Dsat is set, it is
not zero'd out with the beginning of each loop working on the current
job indexed by integer c and remains for the rest of the saturation
calculations.

This got me to thinking. If I reverse the order of the creation of
workStream_A and workStream_B, the code that I supplied initially
should work. I modified to code to read:

8<--------------------------
#!/usr/bin/python
import pdq

pdq.Init("arrivalRate = 38.4")

# Define all of the queues

pdq.CreateNode("cpu", pdq.CEN, pdq.FCFS)
pdq.CreateNode("nic.sent", pdq.CEN, pdq.FCFS)
pdq.CreateNode("nic.recv", pdq.CEN, pdq.FCFS)
pdq.CreateNode("dat.cpu", pdq.CEN, pdq.FCFS)

# Create workStreamB and set the related service demands

pdq.streams = pdq.CreateOpen("workStream_B", 34.56)

pdq.SetDemand("cpu", "workStream_B", 0.00725)
pdq.SetDemand("nic.sent", "workStream_B", 0.00324)
pdq.SetDemand("nic.recv", "workStream_B", 0.00006)
pdq.SetDemand("dat.cpu", "workStream_B", 0.000982)

# Create workStreamA and set the related service demands

pdq.streams = pdq.CreateOpen("workStream_A", 3.84)

pdq.SetDemand("cpu", "workStream_A", 0.029)
pdq.SetDemand("nic.sent", "workStream_A", 0.01296)
pdq.SetDemand("nic.recv", "workStream_A", 0.00006)
pdq.SetDemand("dat.cpu", "workStream_A", 0.001379)

pdq.Solve(pdq.CANON)
pdq.Report()
-------------------------->8

And now the code works as expected. Huzzah! I just tested this on
another laptop and can't cut and paste the results, but I saw the
report generated correctly with expected results.

Now the question is how to prevent this from happening again? I think
it could easily be remedied by setting Dsat to zero at the beginning
of each for loop that will be working on each job (line 57 of
MVA_Cannon.c):

8<---------------- begins on line 57 of MVA_Cannon.c ---------
for (c = 0; c < streams; c++) {
Dsat = 0.0; // Added by James to reset the Dsat with each job to
ensure the correct bottlenecks are identified per job.
sumR[c] = 0.0;
X = job[c].trans->arrival_rate;
------------------------------------------------------------------------------
>8

Once again, I have not actually tried this yet under my Linux VM.
Perhaps I'll give it a try tonight when I have more free time. Now I
have to go back to doing what a real engineer does these days:
Generating PowerPoint presentations for managers.

James

James

unread,
Feb 25, 2011, 5:32:51 PM2/25/11
to Guerrilla Capacity Planning
I made the changes I discussed in my earlier message and it appears to
be working. The order of creation for workStream_A and workStream_B
are no longer an issue.

Huzzah!

DrQ

unread,
Mar 1, 2011, 10:16:29 PM3/1/11
to Guerrilla Capacity Planning
I've checked in your suggested fix to SourceForge. We then need to
SWIG it and test it, prior to release as PDQ 5.0.4.

I've also introduced a more explicit error msg to help identify any
future problems in multiple workload models:
ERROR in procedure 'canonical()':
Arrival rate 34.560 for stream 'workB' exceeds saturation thruput
34.483 of node 'CPU' with demand 0.029

The real problem for me was, how did this case manage to escape from
the "massive incarceration" that comprises my PDQ test suite?
I do have a multiple stream/multiple node example, but it just so
happens that the parameter values are sufficiently below thresholds
like saturation, that it didn't show up. You can never be too careful.

James

unread,
Mar 1, 2011, 11:16:14 PM3/1/11
to Guerrilla Capacity Planning
Woo-hoo! My first contribution to an open source project WRT coding
(albeit it one line of code).

James

DrQ

unread,
Mar 5, 2011, 2:21:51 PM3/5/11
to Guerrilla Capacity Planning
See http://www.perfdynamics.com/Tools/PDQcode.html#tth_sEc1.1 for
status of release 5.0.4 for PDQ.

James Newsom

unread,
Mar 5, 2011, 3:24:39 PM3/5/11
to guerrilla-cap...@googlegroups.com

Yay!

> --
> You received this message because you are subscribed to the Google Groups "Guerrilla Capacity Planning" group.
> To post to this group, send email to guerrilla-cap...@googlegroups.com.
> To unsubscribe from this group, send email to guerrilla-capacity-...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/guerrilla-capacity-planning?hl=en.
>
Reply all
Reply to author
Forward
0 new messages