File: http-agent.tcl

package info (click to toggle)
ns2 2.35%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 78,756 kB
  • ctags: 27,476
  • sloc: cpp: 172,923; tcl: 107,130; perl: 6,391; sh: 6,143; ansic: 5,846; makefile: 812; awk: 525; csh: 355
file content (732 lines) | stat: -rw-r--r-- 22,195 bytes parent folder | download | duplicates (8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
# Copyright (c) Xerox Corporation 1998. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
# 
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# 
# Linking this file statically or dynamically with other modules is making
# a combined work based on this file.  Thus, the terms and conditions of
# the GNU General Public License cover the whole combination.
# 
# In addition, as a special exception, the copyright holders of this file
# give you permission to combine this file with free software programs or
# libraries that are released under the GNU LGPL and with code included in
# the standard release of ns-2 under the Apache 2.0 license or under
# otherwise-compatible licenses with advertising requirements (or modified
# versions of such code, with unchanged license).  You may copy and
# distribute such a system following the terms of the GNU GPL for this
# file and the licenses of the other code concerned, provided that you
# include the source code of that other code when and as the GNU GPL
# requires distribution of source code.
# 
# Note that people who make modified versions of this file are not
# obligated to grant this special exception for their modified versions;
# it is their choice whether to do so.  The GNU General Public License
# gives permission to release a modified version without this exception;
# this exception also makes it possible to release a modified version
# which carries forward this exception.
#
# HTTP agents: server, client, cache
#
# $Header: /cvsroot/nsnam/ns-2/tcl/webcache/http-agent.tcl,v 1.11 2005/08/26 05:05:30 tomh Exp $


Http set id_ 0	;# required by TclCL
# Type of Tcp agent. Can be SimpleTcp or FullTcp
# Default should be set to FullTcp, in case for inadvertent bites. :(
Http set TRANSPORT_ FullTcp
Http set HB_FID_ 40
Http set PINV_FID_ 41

# XXX invalidation message size should be proportional to the number of
# invalidations inside the message
Http set INVSize_ 43	;# unicast invalidation
Http set REQSize_ 43	;# Request
Http set REFSize_ 50	;# Refetch request
Http set IMSSize_ 50	;# If-Modified-Since
Http set JOINSize_ 10	;# Server join/leave
Http set HBSize_ 1	;# Used by Http/Server/Inval only
Http set PFSize_ 1	;# Pro forma
Http set NTFSize_ 10	;# Request Notification
Http set MPUSize_ 10	;# Mandatory push request

Http/Server set id_ 0
Http/Server/Inval set id_ 0
Http/Server/Inval/Yuc set hb_interval_ 60
Http/Server/Inval/Yuc set enable_upd_ 0
Http/Server/Inval/Yuc set Ca_ 1
Http/Server/Inval/Yuc set Cb_ 4
Http/Server/Inval/Yuc set push_thresh_ 4
Http/Server/Inval/Yuc set push_low_bound_ 0
Http/Server/Inval/Yuc set push_high_bound_ 8

Http/Cache set id_ 0
Http/Cache/Inval set id_ 0
Http/Cache/Inval/Mcast set hb_interval_ 60
Http/Cache/Inval/Mcast set upd_interval_ 5
Http/Cache/Inval/Mcast set enable_upd_ 0
Http/Cache/Inval/Mcast set Ca_ 1
Http/Cache/Inval/Mcast set Cb_ 4
Http/Cache/Inval/Mcast set push_thresh_ 4
Http/Cache/Inval/Mcast set push_low_bound_ 0
Http/Cache/Inval/Mcast set push_high_bound_ 8
Http/Cache/Inval/Mcast/Perc set direct_request_ 0

PagePool/CompMath set num_pages_ 1
PagePool/CompMath set main_size_ 1024
PagePool/CompMath set comp_size_ 10240

# Transport protocol used for multimedia connections
Http set MEDIA_TRANSPORT_ RAP
# Application-level handler for multimedia connections. 
# Currently there are two available:
# - MediaApp: simply extract data from packets and pass it to cache
# - QA: do quality adaptation
Http set MEDIA_APP_ MediaApp
# 1K per multimedia segment
Application/MediaApp set segmentSize_ 1024
Application/MediaApp set MAX_LAYER_ 10
# Constants related to quality adaptation
Application/MediaApp/QA set LAYERBW_ 2500 ;# Byte per-second
Application/MediaApp/QA set MAXACTIVELAYERS_ 10
Application/MediaApp/QA set SRTTWEIGHT_ 0.95
Application/MediaApp/QA set SMOOTHFACTOR_ 4
Application/MediaApp/QA set MAXBKOFF_ 100
Application/MediaApp/QA set debug_output_ 0
# Prefetching lookahead SRTT 200ms
Application/MediaApp/QA set pref_srtt_ 0.6
# 100M buffer size at cache/server/client
PagePool/Client/Media set max_size_ 104857600 


Http instproc init { ns node } {
	$self next
	$self instvar ns_ node_ id_ pool_
	set ns_ $ns
	set node_ $node
	$self set id_ [$node_ id]
	set pool_ [$self create-pagepool]
}

Http instproc create-pagepool {} {
	set pool [new PagePool/Client]
	$self set-pagepool $pool
	return $pool
}

Http instproc addr {} {
	$self instvar node_ 
	return [$node_ node-addr]
}

Http set fid_ -1
Http instproc getfid {} {
	$self instvar fid_
	set fid_ [Http set fid_]
	Http set fid_ [incr fid_]
}

Http instproc get-mpusize {} {
	return [Http set MPUSize_]
}

Http instproc get-ntfsize {} {
	return [Http set NTFSize_]
}

Http instproc get-pfsize {} {
	return [Http set PFSize_]
}

Http instproc get-hbsize {} {
	return [Http set HBSize_]
}

Http instproc get-imssize {} {
	return [Http set IMSSize_]
}

Http instproc get-invsize {} {
	return [Http set INVSize_]
}

# Generate request packet size. Should be constant because it's small
Http instproc get-reqsize {} {
	return [Http set REQSize_]
}

Http instproc get-refsize {} {
	return [Http set REFSize_]
}

Http instproc get-joinsize {} {
	return [Http set JOINSize_]
}

# At startup, connect to a server, the server may be a cache
Http instproc connect { server } {
	Http instvar TRANSPORT_
	$self instvar ns_ slist_ node_ fid_ id_

	lappend slist_ $server
	set tcp [new Agent/TCP/$TRANSPORT_]
	$tcp set fid_ [$self getfid]
	$ns_ attach-agent $node_ $tcp

	set ret [$server alloc-connection $self $fid_]
	set snk [$ret agent]
	$ns_ connect $tcp $snk
	#$tcp set dst_ [$snk set addr_]
	$tcp set window_ 100

	# Use a wrapper to implement application data transfer
	set wrapper [new Application/TcpApp $tcp]
	$self cmd connect $server $wrapper
	$wrapper connect $ret
	#puts "HttpApp $id_ connected to server [$server id]"
}

Http instproc stat { name } {
	$self instvar stat_
	return $stat_($name)
}


# Used for mandatory push refreshments
Http/Client set hb_interval_ 60

Http/Client instproc init args {
	eval $self next $args
	$self instvar node_ stat_
	$node_ color "SteelBlue"
	array set stat_ [list req-num 0 stale-num 0 stale-time 0 rep-time 0 \
		rt-min 987654321 rt-max 0 st-min 987654321 st-max 0]
}

# XXX Assume that it's always client disconnects from server, not vice versa
Http/Client instproc disconnect { server } {
	$self instvar ns_ slist_ 
	set pos [lsearch $slist_ $server]
	if {$pos >= 0} {
		lreplace $slist_ $pos $pos
	} else { 
		error "Http::disconnect: not connected to $server"
	}

	# Cleanup of all pending requests and states
	$self instvar ns_ node_ cache_
	$self stop-session $server

	# XXX Is this the right behavior? Should we wait for FIN etc.?
	set tcp [[$self get-cnc $server] agent]
	$self cmd disconnect $server
	$server disconnect $self
	$tcp proc done {} "$ns_ detach-agent $node_ $tcp; delete $tcp"
	$tcp close
}

# Meta-data to be sent in a request
# XXX pageid should always be given from the users, because client may 
# connect to a cache, hence it doesn't know the server name.
Http/Client instproc send-request { server type pageid args } {
	$self instvar ns_ pending_ 	;# unansewered requests

	# XXX Do not set pending states for an non-existent connection
	if ![$self cmd is-connected $server] {
		return
	}

	if ![info exists pending_($pageid)] { 
		# XXX Actually we should use set, because only one request
		# is allowed for a page simultaneously
		lappend pending_($pageid) [$ns_ now]
	} else {
		# If the page is being requested, do not send another request
		return
	}

	set size [$self get-reqsize]
	$self send $server $size \
	    "$server get-request $self $type $pageid size $size [join $args]"
	$self evTrace C GET p $pageid s [$server id] z $size
	$self instvar stat_ simStartTime_
	if [info exists simStartTime_] {
		incr stat_(req-num)
	}

	$self mark-request $pageid
}

Http/Client instproc mark-request { pageid } {
	# Nam state coloring
	$self instvar node_ marks_ ns_
	$node_ add-mark $pageid:[$ns_ now] "purple"
	lappend marks_($pageid) $pageid:[$ns_ now]
}

# The reason that "type" is here is for Http/Cache to work. Client doesn't 
# check the reason of the response
Http/Client instproc get-response-GET { server pageid args } {
	$self instvar pending_ id_ ns_ stat_ simStartTime_

	if ![info exists pending_($pageid)] {
		error "Client $id_: Unrequested response page $pageid from server [$server id]"
	}

	array set data $args

	# Check stale hits
	set origsvr [lindex [split $pageid :] 0]
	set modtime [$origsvr get-modtime $pageid]
	set reqtime [lindex $pending_($pageid) 0]
	set reqrtt [expr [$ns_ now] - $reqtime]
	
	#
	# XXX If a stale hit occurs because a page is modified during the RTT
	# of the request, we should *NOT* consider it a stale hit. We 
	# implement it by ignoring all stale hits whose modification time is
	# larger than the request time. 
	#
	if {$modtime > $data(modtime)} {
		# Staleness is the time from now to the time it's last modified
		set tmp [$origsvr stale-time $pageid $data(modtime)]
		if {$tmp > $reqrtt/2} {
			# We have a real stale hit
			$self evTrace C STA p $pageid s [$origsvr id] l $tmp
			if [info exists simStartTime_] {
				incr stat_(stale-num)
				set stat_(stale-time) [expr \
					$stat_(stale-time) + $tmp]
				if {$stat_(st-min) > $tmp} {
					set stat_(st-min) $tmp
				}
				if {$stat_(st-max) < $tmp} {
					set stat_(st-max) $tmp
				}
			}
		}
	}

	# Assume this response is for the very first request we've sent. 
	# Because we'll average the response time at the end, which 
	# request this response actually corresponds to doesn't matter.
	$self evTrace C RCV p $pageid s [$server id] l $reqrtt z $data(size)
	if [info exists simStartTime_] {
		set stat_(rep-time) [expr $stat_(rep-time) + $reqrtt]
		if {$stat_(rt-min) > $reqrtt} {
			set stat_(rt-min) $reqrtt
		}
		if {$stat_(rt-max) < $reqrtt} {
			set stat_(rt-max) $reqrtt
		}
	}

	set pending_($pageid) [lreplace $pending_($pageid) 0 0]
	if {[llength $pending_($pageid)] == 0} {
		unset pending_($pageid)
	}
	$self mark-response $pageid
}

Http/Client instproc mark-response { pageid } {
	$self instvar node_ marks_ ns_
	set mk [lindex $marks_($pageid) 0]
	$node_ delete-mark $mk
	set marks_($pageid) [lreplace $marks_($pageid) 0 0]
}

Http/Client instproc get-response-REF { server pageid args } {
	eval $self get-response-GET $server $pageid $args
}

Http/Client instproc get-response-IMS { server pageid args } {
	eval $self get-response-GET $server $pageid $args
}

# Generate the time when next request will occur
# It's either a TracePagePool or a MathPagePool
#
# XXX both TracePagePool and MathPagePool should share the same C++ 
# interface and OTcl interface
Http/Client instproc set-page-generator { pagepool } {
	$self instvar pgtr_ 	;# Page generator
	set pgtr_ $pagepool
}

Http/Client instproc set-interval-generator { ranvar } {
	$self instvar rvInterPage_
	set rvInterPage_ $ranvar
}

# XXX PagePool::gen-pageid{} *MUST* precede gen-req-itvl{}, because the 
# former may responsible for loading a new page from the trace file if 
# PagePool/ProxyTrace is used
Http/Client instproc gen-request {} {
	$self instvar pgtr_ rvInterPage_ id_

	if ![info exists pgtr_] {
		error "Http/Client requires a page generator (pgtr_)!"
	}

	# XXX 
	# rvInterPage_ should only be set when PagePool/ProxyTrace is 
	# *NOT* used
	if [info exists rvInterPage_] {
		return [list [$rvInterPage_ value] [$pgtr_ gen-pageid $id_]]
	} else {
		return [$pgtr_ gen-request $id_]
	}
}

Http/Client instproc next-request { server pageid } {
	$self instvar ns_ cache_ nextreq_

	if [info exists cache_] {
		$self send-request $cache_ GET $pageid
	} else {
		$self send-request $server GET $pageid
	}

	# Prepare for the next request
	set req [$self gen-request]
	set pageid $server:[lindex $req 1]
	set itvl [lindex $req 0]
	if {$itvl >= 0} {
		set nextreq_([$server id]) [$ns_ at [expr [$ns_ now] + $itvl] \
			"$self next-request $server $pageid"]
	} ;# otherwise it's the end of the request stream 
}

Http/Client instproc set-cache { cache } {
	$self instvar cache_
	set cache_ $cache
}

# Assuming everything is setup, this function starts sending requests
# Sending a request to $cache, the original page should come from $server
#
# Populate a cache with all available pages
# XXX how would we distribute pages spatially when we have a hierarchy 
# of caches? Or, how would we distribute client requests spatially?
# It should be used in single client, single cache and single server case
# *ONLY*.
Http/Client instproc start-session { cache server } {
	$self instvar ns_ cache_ simStartTime_

	$self instvar simStartTime_ pgtr_
	set simStartTime_ [$ns_ now]
	if [info exists pgtr_] {
		if {[$pgtr_ get-start-time] > $simStartTime_} {
			$pgtr_ set-start-time $simStartTime_
		}
	}

	#puts "Client [$self id] starts request session at [$ns_ now]"

	set cache_ $cache

	# The first time invocation we don't send out a request 
	# immediately. Instead, we find out when will the first
	# request happen, then schedule it using next-request{}
	# Then every time next-request{} is called later, it
	# sends out a request and schedule the next.
	set req [$self gen-request]
	set pageid $server:[lindex $req 1]
	set itvl [lindex $req 0]
	if {$itvl >= 0} {
		$ns_ at [expr [$ns_ now] + $itvl] \
			"$self next-request $server $pageid"
	} ;# otherwise it's the end of the request stream 
}

# Stop sending further requests, and clean all pending requests
Http/Client instproc stop-session { server } {
	$self instvar ns_ nextreq_ pending_ cache_
	set sid [$server id]

	# Clean up all pending requests
	if [info exists nextreq_($sid)] {
		$ns_ cancel $nextreq_($sid)
	}
	if {![info exists pending_]} {
		return
	}
	# XXX If a client either connects to a *SINGLE* cache 
	# or a *SINGLE* server, then we remove all pending requests 
	# when we are disconnected. 
	if {[info exists cache_] && ($server == $cache_)} {
		unset pending_
	} else {
		# Only remove pending requests related to $server
		foreach p [array names pending_] {
			if {$server == [lindex [split $p :] 0]} {
				unset pending_($p)
			}
		}
	}
}

Http/Client instproc populate { cache server } {
	$self instvar pgtr_ curpage_ status_ ns_

	if ![info exists status_] {
		set status_ "POPULATE"
		set curpage_ 0
	}

	if [info exists pgtr_] {
		# Populate cache with all pages incrementally
		if {$curpage_ < [$pgtr_ get-poolsize]} {
			$self send-request $cache GET $server:$curpage_
			incr curpage_
			$ns_ at [expr [$ns_ now] + 1] \
				"$self populate $cache $server"
#			if {$curpage_ % 1000 == 0} {
#				puts "Client [$self id] populated $curpage_"
#			}
			return
		}
	}

	# Start sending requests
	$ns_ at [expr [$ns_ now] + 10] "$self start-session $cache $server"
}

Http/Client instproc start { cache server } {
	$self instvar cache_
	set cache_ $cache
	$self populate $cache $server
}

Http/Client instproc request-mpush { page } {
	$self instvar mpush_refresh_ ns_ cache_
	$self send $cache_ [$self get-mpusize] \
		"$cache_ request-mpush $page"
	Http/Client instvar hb_interval_
	set mpush_refresh_($page) [$ns_ at [expr [$ns_ now] + $hb_interval_] \
		"$self send-refresh-mpush $page"]
}

Http/Client instproc send-refresh-mpush { page } {
	$self instvar mpush_refresh_ ns_ cache_
	$self send $cache_ [$self get-mpusize] "$cache_ refresh-mpush $page"
	#puts "[$ns_ now]: Client [$self id] send mpush refresh"
	Http/Client instvar hb_interval_
	set mpush_refresh_($page) [$ns_ at [expr [$ns_ now] + $hb_interval_] \
		"$self send-refresh-mpush $page"]
}

# XXX We use explicit teardown. 
Http/Client instproc stop-mpush { page } {
	$self instvar mpush_refresh_ ns_ cache_

	if [info exists mpush_refresh_($page)] {
		# Stop sending the periodic refreshment
		$ns_ cancel $mpush_refresh_($page)
		#puts "[$ns_ now]: Client [$self id] stops mpush"
	} else {
		error "no mpush to cancel!"
	}
	# Send explicit message up to tear down
	$self send $cache_ [$self get-mpusize] "$cache_ stop-mpush $page"
}


#----------------------------------------------------------------------
# Client which is capable of handling compound pages
#----------------------------------------------------------------------
Class Http/Client/Compound -superclass Http/Client

Http/Client/Compound instproc set-interobj-generator { ranvar } {
	$self instvar rvInterObj_
	set rvInterObj_ $ranvar
}

# Generate next page request and the number of embedded objects for 
# this page. 
# Use rvInterPage_ to generate interval between pages (inactive OFF), and 
# use rvInterObj_ to generate interval between embedded objects (active OFF)
Http/Client/Compound instproc next-request { server pageid } {
	# First schedule next page request
	eval $self next $server $pageid

}

# Request the next embedded object(s)
Http/Client/Compound instproc next-obj { server args } {
	$self instvar pgtr_ cache_ req_objs_ ns_ rvInterObj_

	if ![llength $args] {
		# No requests to make
		return
	}

	if [info exists cache_] {
		set dest $cache_
	} else {
		set dest $server
	}

	set pageid [lindex $args 0]
	set mpgid [$pgtr_ get-mainpage $pageid] ;# main page id
	set max 0
	set origsvr [lindex [split $pageid :] 0]

	foreach pageid $args {
		set id [lindex [split $pageid :] 1]
		if {$max < $id} {
			set max $id
		}
		incr req_objs_($mpgid) -1
		$self send-request $dest GET $pageid
	}
	if {$req_objs_($mpgid) <= 0} {
		# We have requested all objects for this page, done.
		return
	}

	# Schedule next requests. Assuming we get embedded objects according
	# to the ascending order of their page id
	set objid [join [$pgtr_ get-next-objs $origsvr:$max]]
	puts "At [$ns_ now], client [$self id] get objs $objid"
	if [info exists rvInterObj_] {
		$ns_ at [expr [$ns_ now] + [$rvInterObj_ value]] \
			"$self next-obj $server $objid"
	} else {
		$self next-obj $server $objid
	}
}

# XXX We need to override get-response-GET because we need to recompute
#     the RCV time, and the STA time, etc. 
# XXX Allow only *ONE* compound page.
Http/Client/Compound instproc get-response-GET { server pageid args } {
	$self instvar pending_ id_ ns_ recv_objs_ max_stale_ stat_ \
			simStartTime_ pgtr_

	if ![info exists pending_($pageid)] {
		error "Client $id_: Unrequested response page $pageid from server/cache [$server id]"
	}

	# Check if this is the main page
	if [$pgtr_ is-mainpage $pageid] {
		set mpgid $pageid
		# Get all the embedded objects, do "active OFF" delay 
		# if available
		$self instvar req_objs_ recv_objs_ rvInterObj_
		# Objects that are to be received
		set recv_objs_($pageid) [$pgtr_ get-obj-num $pageid] 
		# Objects that have been requested
		set req_objs_($pageid) $recv_objs_($pageid) 
		set objid [join [$pgtr_ get-next-objs $pageid]]
		if [info exists rvInterObj_] {
			$ns_ at [expr [$ns_ now] + [$rvInterObj_ value]] \
					"$self next-obj $server $objid"
		} else {
			eval $self next-obj $server $objid
		}
	} else {
		# Main page id
		set mpgid [$pgtr_ get-mainpage $pageid]
	}

	array set data $args

	# Check stale hits and record maximum stale hit time
	set origsvr [lindex [split $pageid :] 0]
	set modtime [$origsvr get-modtime $pageid]
	set reqtime [lindex $pending_($pageid) 0]
	set reqrtt [expr [$ns_ now] - $reqtime]
	# XXX If a stale hit occurs because a page is modified during the RTT
	# of the request, we should *NOT* consider it a stale hit. We 
	# implement it by ignoring all stale hits whose modification time is
	# larger than the request time. 
	#
	# See Http/Client::get-response-GET{}
	if {$modtime > $data(modtime)} {
		$self instvar ns_
		# Staleness is the time from now to the time it's last modified
		set tmp [$origsvr stale-time $pageid $data(modtime)]
		if {$tmp > $reqrtt/2} {
			if ![info exists max_stale_($mpgid)] {
				set max_stale_($mpgid) $tmp
			} elseif {$max_stale_($mpgid) < $tmp} {
				set max_stale_($mpgid) $tmp
			}
		}
	}

	# Wait for the embedded objects if this is a main page
	if [$pgtr_ is-mainpage $pageid] {
		return
	}

	# Delete pending record of all embedded objects, but not the main page;
	# we need it later to compute the response time, etc.
	# XXX assuming only one request per object
	$self evTrace C RCV p $pageid s [$server id] l $reqrtt z $data(size)
	unset pending_($pageid)

	# Check if we have any pending embedded objects
	incr recv_objs_($mpgid) -1
	if {$recv_objs_($mpgid) > 0} {
		# We are waiting for more objects to come
		return
	}

	# Now we've received all objects
	$self instvar pgtr_
	# Record response time for the entire compound page
	set reqtime [lindex $pending_($mpgid) 0]
	$self evTrace C RCV p $mpgid s [$origsvr id] l \
			[expr [$ns_ now] - $reqtime] z $data(size)
	# We are done with this page
	unset pending_($mpgid)

	if [info exists simStartTime_] {
		set tmp [expr [$ns_ now] - $reqtime]
		set stat_(rep-time) [expr $stat_(rep-time) + $tmp]
		if {$stat_(rt-min) > $tmp} {
			set stat_(rt-min) $tmp
		}
		if {$stat_(rt-max) < $tmp} {
			set stat_(rt-max) $tmp
		}
		unset tmp
	}
	if [info exists max_stale_($mpgid)] {
		$self evTrace C STA p $mpgid s [$origsvr id] \
				l $max_stale_($mpgid)
		if [info exists simStartTime_] {
			incr stat_(stale-num)
			set stat_(stale-time) [expr \
				$stat_(stale-time) + $max_stale_($mpgid)]
			if {$stat_(st-min) > $max_stale_($mpgid)} {
				set stat_(st-min) $max_stale_($mpgid)
			}
			if {$stat_(st-max) < $max_stale_($mpgid)} {
				set stat_(st-max) $max_stale_($mpgid)
			}
		}
		unset max_stale_($mpgid)
	}
	$self mark-response $mpgid
}

Http/Client/Compound instproc mark-request { pageid } {
	set id [lindex [split $pageid :] end]
	if {$id == 0} {
		$self next $pageid
	}
}