aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs
diff options
context:
space:
mode:
authorUbitUmarov2015-09-02 19:54:53 +0100
committerUbitUmarov2015-09-02 19:54:53 +0100
commita11edceb00b5b86f825bd957bdac9edb91f893dd (patch)
treec192eae26f3aadf365a66f32fc6d9ade2f0a0c61 /OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs
parentbad merge? (diff)
downloadopensim-SC-a11edceb00b5b86f825bd957bdac9edb91f893dd.zip
opensim-SC-a11edceb00b5b86f825bd957bdac9edb91f893dd.tar.gz
opensim-SC-a11edceb00b5b86f825bd957bdac9edb91f893dd.tar.bz2
opensim-SC-a11edceb00b5b86f825bd957bdac9edb91f893dd.tar.xz
seems to compile ( tests comented out)
Diffstat (limited to 'OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs')
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs271
1 files changed, 39 insertions, 232 deletions
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs b/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs
index 7a42d82..14099fe 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs
@@ -42,24 +42,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP
42 public class TokenBucket 42 public class TokenBucket
43 { 43 {
44 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 44 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
45<<<<<<< HEAD
46 45
47 public string Identifier { get; private set; }
48
49 public int DebugLevel { get; set; }
50
51 /// <summary>
52 /// Number of ticks (ms) per quantum, drip rate and max burst
53 /// are defined over this interval.
54 /// </summary>
55 protected const Int32 m_ticksPerQuantum = 1000;
56=======
57 private static Int32 m_counter = 0; 46 private static Int32 m_counter = 0;
58 47
59// private Int32 m_identifier; 48// private Int32 m_identifier;
60 49
61 protected const float m_timeScale = 1e-3f; 50 protected const float m_timeScale = 1e-3f;
62>>>>>>> avn/ubitvar
63 51
64 /// <summary> 52 /// <summary>
65 /// This is the number of m_minimumDripRate bytes 53 /// This is the number of m_minimumDripRate bytes
@@ -72,11 +60,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
72 60
73 /// <summary> 61 /// <summary>
74 /// </summary> 62 /// </summary>
75<<<<<<< HEAD
76 protected const Int32 m_minimumDripRate = LLUDPServer.MTU;
77=======
78 protected const float m_minimumDripRate = 1400; 63 protected const float m_minimumDripRate = 1400;
79>>>>>>> avn/ubitvar
80 64
81 /// <summary>Time of the last drip, in system ticks</summary> 65 /// <summary>Time of the last drip, in system ticks</summary>
82 protected Int32 m_lastDrip; 66 protected Int32 m_lastDrip;
@@ -90,27 +74,31 @@ namespace OpenSim.Region.ClientStack.LindenUDP
90 /// <summary> 74 /// <summary>
91 /// Map of children buckets and their requested maximum burst rate 75 /// Map of children buckets and their requested maximum burst rate
92 /// </summary> 76 /// </summary>
93<<<<<<< HEAD 77
94 protected Dictionary<TokenBucket,Int64> m_children = new Dictionary<TokenBucket,Int64>();
95=======
96 protected Dictionary<TokenBucket, float> m_children = new Dictionary<TokenBucket, float>(); 78 protected Dictionary<TokenBucket, float> m_children = new Dictionary<TokenBucket, float>();
97 79
98#region Properties 80#region Properties
99>>>>>>> avn/ubitvar
100 81
101 /// <summary> 82 /// <summary>
102 /// The parent bucket of this bucket, or null if this bucket has no 83 /// The parent bucket of this bucket, or null if this bucket has no
103 /// parent. The parent bucket will limit the aggregate bandwidth of all 84 /// parent. The parent bucket will limit the aggregate bandwidth of all
104 /// of its children buckets 85 /// of its children buckets
105 /// </summary> 86 /// </summary>
106 public TokenBucket Parent { get; protected set; } 87 protected TokenBucket m_parent;
107 88 public TokenBucket Parent
89 {
90 get { return m_parent; }
91 set { m_parent = value; }
92 }
108 /// <summary> 93 /// <summary>
109 /// This is the maximum number 94 /// This is the maximum number
110 /// of tokens that can accumulate in the bucket at any one time. This 95 /// of tokens that can accumulate in the bucket at any one time. This
111 /// also sets the total request for leaf nodes 96 /// also sets the total request for leaf nodes
112 /// </summary> 97 /// </summary>
113 protected float m_burst; 98 protected float m_burst;
99//not in use
100 public float MaxDripRate { get; set; }
101
114 public float RequestedBurst 102 public float RequestedBurst
115 { 103 {
116 get { return m_burst; } 104 get { return m_burst; }
@@ -143,63 +131,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
143 /// Can never be above MaxDripRate. 131 /// Can never be above MaxDripRate.
144 /// Tokens are added to the bucket at any time 132 /// Tokens are added to the bucket at any time
145 /// <seealso cref="RemoveTokens"/> is called, at the granularity of 133 /// <seealso cref="RemoveTokens"/> is called, at the granularity of
146<<<<<<< HEAD
147 /// the system tick interval (typically around 15-22ms)
148 /// FIXME: It is extremely confusing to be able to set a RequestedDripRate of 0 and then receive a positive
149 /// number on get if TotalDripRequest is set. This also stops us being able to retrieve the fact that
150 /// RequestedDripRate is set to 0. Really, this should always return m_dripRate and then we can get
151 /// (m_dripRate == 0 ? TotalDripRequest : m_dripRate) on some other properties.
152 /// </remarks>
153 public virtual Int64 RequestedDripRate
154 {
155 get { return (m_dripRate == 0 ? TotalDripRequest : m_dripRate); }
156 set
157 {
158 if (value <= 0)
159 m_dripRate = 0;
160 else if (MaxDripRate > 0 && value > MaxDripRate)
161 m_dripRate = MaxDripRate;
162 else
163 m_dripRate = value;
164
165 m_burstRate = (Int64)((double)m_dripRate * m_quantumsPerBurst);
166
167 if (Parent != null)
168 Parent.RegisterRequest(this, m_dripRate);
169 }
170 }
171
172 /// <summary>
173 /// Gets the drip rate.
174 /// </summary>
175 /// <value>
176 /// DripRate can never be above max drip rate or below min drip rate.
177 /// If we are a child bucket then the drip rate return is modifed by the total load on the capacity of the
178 /// parent bucket.
179 /// </value>
180 public virtual Int64 DripRate
181 {
182 get
183 {
184 double rate;
185
186 // FIXME: This doesn't properly work if we have a parent and children and a requested drip rate set
187 // on ourselves which is not equal to the child drip rates.
188 if (Parent == null)
189 {
190 if (TotalDripRequest > 0)
191 rate = Math.Min(RequestedDripRate, TotalDripRequest);
192 else
193 rate = RequestedDripRate;
194 }
195 else
196 {
197 rate = (double)RequestedDripRate * Parent.DripRateModifier();
198 }
199
200=======
201 /// the system tick interval (typically around 15-22ms)</remarks> 134 /// the system tick interval (typically around 15-22ms)</remarks>
202 protected float m_dripRate; 135 protected float m_dripRate;
136
203 public virtual float RequestedDripRate 137 public virtual float RequestedDripRate
204 { 138 {
205 get { return (m_dripRate == 0 ? m_totalDripRequest : m_dripRate); } 139 get { return (m_dripRate == 0 ? m_totalDripRequest : m_dripRate); }
@@ -212,7 +146,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
212 } 146 }
213 } 147 }
214 148
215 public virtual float DripRate 149 public virtual float DripRate
216 { 150 {
217 get { 151 get {
218 float rate = Math.Min(RequestedDripRate,TotalDripRequest); 152 float rate = Math.Min(RequestedDripRate,TotalDripRequest);
@@ -220,28 +154,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP
220 return rate; 154 return rate;
221 155
222 rate *= m_parent.DripRateModifier(); 156 rate *= m_parent.DripRateModifier();
223>>>>>>> avn/ubitvar
224 if (rate < m_minimumDripRate) 157 if (rate < m_minimumDripRate)
225 rate = m_minimumDripRate; 158 rate = m_minimumDripRate;
226 else if (MaxDripRate > 0 && rate > MaxDripRate)
227 rate = MaxDripRate;
228 159
229 return (float)rate; 160 return (float)rate;
230 } 161 }
231 } 162 }
232 protected Int64 m_dripRate;
233
234 // <summary>
235 // The maximum rate for flow control. Drip rate can never be greater than this.
236 // </summary>
237 public Int64 MaxDripRate { get; set; }
238 163
239 /// <summary> 164 /// <summary>
240 /// The current total of the requested maximum burst rates of children buckets. 165 /// The current total of the requested maximum burst rates of children buckets.
241 /// </summary> 166 /// </summary>
242<<<<<<< HEAD
243 public Int64 TotalDripRequest { get; protected set; }
244=======
245 protected float m_totalDripRequest; 167 protected float m_totalDripRequest;
246 public float TotalDripRequest 168 public float TotalDripRequest
247 { 169 {
@@ -252,7 +174,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
252#endregion Properties 174#endregion Properties
253 175
254#region Constructor 176#region Constructor
255>>>>>>> avn/ubitvar 177
256 178
257 /// <summary> 179 /// <summary>
258 /// Default constructor 180 /// Default constructor
@@ -260,36 +182,24 @@ namespace OpenSim.Region.ClientStack.LindenUDP
260 /// <param name="identifier">Identifier for this token bucket</param> 182 /// <param name="identifier">Identifier for this token bucket</param>
261 /// <param name="parent">Parent bucket if this is a child bucket, or 183 /// <param name="parent">Parent bucket if this is a child bucket, or
262 /// null if this is a root bucket</param> 184 /// null if this is a root bucket</param>
263<<<<<<< HEAD
264 /// <param name="requestedDripRate">
265 /// Requested rate that the bucket fills, in bytes per
266 /// second. If zero, the bucket always remains full.
267 /// </param>
268 public TokenBucket(string identifier, TokenBucket parent, Int64 requestedDripRate, Int64 maxDripRate)
269=======
270 /// <param name="maxBurst">Maximum size of the bucket in bytes, or 185 /// <param name="maxBurst">Maximum size of the bucket in bytes, or
271 /// zero if this bucket has no maximum capacity</param> 186 /// zero if this bucket has no maximum capacity</param>
272 /// <param name="dripRate">Rate that the bucket fills, in bytes per 187 /// <param name="dripRate">Rate that the bucket fills, in bytes per
273 /// second. If zero, the bucket always remains full</param> 188 /// second. If zero, the bucket always remains full</param>
274 public TokenBucket(TokenBucket parent, float dripRate, float MaxBurst) 189 public TokenBucket(TokenBucket parent, float dripRate, float MaxBurst)
275>>>>>>> avn/ubitvar
276 { 190 {
277 Identifier = identifier; 191 m_counter++;
278 192
279 Parent = parent; 193 Parent = parent;
280<<<<<<< HEAD
281 RequestedDripRate = requestedDripRate;
282 MaxDripRate = maxDripRate;
283 m_lastDrip = Util.EnvironmentTickCount();
284=======
285 RequestedDripRate = dripRate; 194 RequestedDripRate = dripRate;
286 RequestedBurst = MaxBurst; 195 RequestedBurst = MaxBurst;
287 // TotalDripRequest = dripRate; // this will be overwritten when a child node registers 196 // TotalDripRequest = dripRate; // this will be overwritten when a child node registers
288 // MaxBurst = (Int64)((double)dripRate * m_quantumsPerBurst); 197 // MaxBurst = (Int64)((double)dripRate * m_quantumsPerBurst);
289 m_lastDrip = Util.EnvironmentTickCount() + 100000; 198 m_lastDrip = Util.EnvironmentTickCount() + 100000;
290>>>>>>> avn/ubitvar
291 } 199 }
292 200
201#endregion Constructor
202
293 /// <summary> 203 /// <summary>
294 /// Compute a modifier for the MaxBurst rate. This is 1.0, meaning 204 /// Compute a modifier for the MaxBurst rate. This is 1.0, meaning
295 /// no modification if the requested bandwidth is less than the 205 /// no modification if the requested bandwidth is less than the
@@ -299,20 +209,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
299 /// </summary> 209 /// </summary>
300 protected float DripRateModifier() 210 protected float DripRateModifier()
301 { 211 {
302<<<<<<< HEAD
303 Int64 driprate = DripRate;
304 double modifier = driprate >= TotalDripRequest ? 1.0 : (double)driprate / (double)TotalDripRequest;
305
306// if (DebugLevel > 0)
307// m_log.DebugFormat(
308// "[TOKEN BUCKET]: Returning drip modifier {0}/{1} = {2} from {3}",
309// driprate, TotalDripRequest, modifier, Identifier);
310
311 return modifier;
312=======
313 float driprate = DripRate; 212 float driprate = DripRate;
314 return driprate >= TotalDripRequest ? 1.0f : driprate / TotalDripRequest; 213 return driprate >= TotalDripRequest ? 1.0f : driprate / TotalDripRequest;
315>>>>>>> avn/ubitvar
316 } 214 }
317 215
318 /// <summary> 216 /// <summary>
@@ -335,29 +233,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP
335 { 233 {
336 m_children[child] = request; 234 m_children[child] = request;
337 235
338<<<<<<< HEAD
339 TotalDripRequest = 0;
340 foreach (KeyValuePair<TokenBucket, Int64> cref in m_children)
341 TotalDripRequest += cref.Value;
342=======
343 m_totalDripRequest = 0; 236 m_totalDripRequest = 0;
344 foreach (KeyValuePair<TokenBucket, float> cref in m_children) 237 foreach (KeyValuePair<TokenBucket, float> cref in m_children)
345 m_totalDripRequest += cref.Value; 238 m_totalDripRequest += cref.Value;
346>>>>>>> avn/ubitvar
347 } 239 }
348 240
349 // Pass the new values up to the parent 241 // Pass the new values up to the parent
350 if (Parent != null) 242 if (m_parent != null)
351 { 243 m_parent.RegisterRequest(this, Math.Min(RequestedDripRate, TotalDripRequest));
352 Int64 effectiveDripRate;
353
354 if (RequestedDripRate > 0)
355 effectiveDripRate = Math.Min(RequestedDripRate, TotalDripRequest);
356 else
357 effectiveDripRate = TotalDripRequest;
358
359 Parent.RegisterRequest(this, effectiveDripRate);
360 }
361 } 244 }
362 245
363 /// <summary> 246 /// <summary>
@@ -370,15 +253,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
370 { 253 {
371 m_children.Remove(child); 254 m_children.Remove(child);
372 255
373<<<<<<< HEAD
374 TotalDripRequest = 0;
375 foreach (KeyValuePair<TokenBucket, Int64> cref in m_children)
376 TotalDripRequest += cref.Value;
377=======
378 m_totalDripRequest = 0; 256 m_totalDripRequest = 0;
379 foreach (KeyValuePair<TokenBucket, float> cref in m_children) 257 foreach (KeyValuePair<TokenBucket, float> cref in m_children)
380 m_totalDripRequest += cref.Value; 258 m_totalDripRequest += cref.Value;
381>>>>>>> avn/ubitvar
382 } 259 }
383 260
384 // Pass the new values up to the parent 261 // Pass the new values up to the parent
@@ -427,7 +304,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
427 // with no drip rate... 304 // with no drip rate...
428 if (DripRate == 0) 305 if (DripRate == 0)
429 { 306 {
430 m_log.WarnFormat("[TOKENBUCKET] something odd is happening and drip rate is 0 for {0}", Identifier); 307 m_log.WarnFormat("[TOKENBUCKET] something odd is happening and drip rate is 0 for {0}", m_counter);
431 return; 308 return;
432 } 309 }
433 310
@@ -453,17 +330,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
453 public bool AdaptiveEnabled { get; set; } 330 public bool AdaptiveEnabled { get; set; }
454 331
455 /// <summary> 332 /// <summary>
456<<<<<<< HEAD
457 /// Target drip rate for this bucket.
458 /// </summary>
459 /// <remarks>Usually set by the client. If adaptive is enabled then throttles will increase until we reach this.</remarks>
460 public Int64 TargetDripRate
461 {
462 get { return m_targetDripRate; }
463 set
464 {
465 m_targetDripRate = Math.Max(value, m_minimumFlow);
466=======
467 /// The minimum rate for flow control. Minimum drip rate is one 333 /// The minimum rate for flow control. Minimum drip rate is one
468 /// packet per second. 334 /// packet per second.
469 /// </summary> 335 /// </summary>
@@ -479,13 +345,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP
479 public float MaxDripRate 345 public float MaxDripRate
480 { 346 {
481 get { return (m_maxDripRate == 0 ? m_totalDripRequest : m_maxDripRate); } 347 get { return (m_maxDripRate == 0 ? m_totalDripRequest : m_maxDripRate); }
482 set 348 set
483 { 349 {
484 m_maxDripRate = (value == 0 ? m_totalDripRequest : Math.Max(value, m_minimumFlow)); 350 m_maxDripRate = (value == 0 ? m_totalDripRequest : Math.Max(value, m_minimumFlow));
485>>>>>>> avn/ubitvar
486 } 351 }
487 } 352 }
488 protected Int64 m_targetDripRate; 353
354 private bool m_enabled = false;
489 355
490 // <summary> 356 // <summary>
491 // Adjust drip rate in response to network conditions. 357 // Adjust drip rate in response to network conditions.
@@ -493,109 +359,50 @@ namespace OpenSim.Region.ClientStack.LindenUDP
493 public virtual float AdjustedDripRate 359 public virtual float AdjustedDripRate
494 { 360 {
495 get { return m_dripRate; } 361 get { return m_dripRate; }
496<<<<<<< HEAD 362 set
497 set
498 { 363 {
499 m_dripRate = OpenSim.Framework.Util.Clamp<Int64>(value, m_minimumFlow, TargetDripRate); 364 m_dripRate = OpenSim.Framework.Util.Clamp<float>(value, m_minimumFlow, MaxDripRate);
500 m_burstRate = (Int64)((double)m_dripRate * m_quantumsPerBurst);
501
502 if (Parent != null)
503 Parent.RegisterRequest(this, m_dripRate);
504=======
505 set {
506 m_dripRate = OpenSim.Framework.Util.Clamp<float>(value,m_minimumFlow,MaxDripRate);
507 365
508 if (m_parent != null) 366 if (m_parent != null)
509 m_parent.RegisterRequest(this,m_dripRate); 367 m_parent.RegisterRequest(this, m_dripRate);
510>>>>>>> avn/ubitvar
511 } 368 }
512 } 369 }
513 370
514 /// <summary> 371
515 /// The minimum rate for adaptive flow control.
516 /// </summary>
517 protected Int64 m_minimumFlow = 32000;
518
519<<<<<<< HEAD
520 /// <summary>
521 /// Constructor for the AdaptiveTokenBucket class
522 /// <param name="identifier">Unique identifier for the client</param>
523 /// <param name="parent">Parent bucket in the hierarchy</param>
524 /// <param name="requestedDripRate"></param>
525 /// <param name="maxDripRate">The ceiling rate for adaptation</param>
526 /// <param name="minDripRate">The floor rate for adaptation</param>
527 /// </summary>
528 public AdaptiveTokenBucket(string identifier, TokenBucket parent, Int64 requestedDripRate, Int64 maxDripRate, Int64 minDripRate, bool enabled)
529 : base(identifier, parent, requestedDripRate, maxDripRate)
530 {
531 AdaptiveEnabled = enabled;
532
533 if (AdaptiveEnabled)
534 {
535// m_log.DebugFormat("[TOKENBUCKET]: Adaptive throttle enabled");
536 m_minimumFlow = minDripRate;
537 TargetDripRate = m_minimumFlow;
538 AdjustedDripRate = m_minimumFlow;
539 }
540=======
541 // <summary> 372 // <summary>
542 // 373 //
543 // </summary> 374 // </summary>
544 public AdaptiveTokenBucket(TokenBucket parent, float maxDripRate,float maxBurst, bool enabled) 375 public AdaptiveTokenBucket(TokenBucket parent, float maxDripRate, float maxBurst, bool enabled)
545 : base(parent, maxDripRate,maxBurst) 376 : base(parent, maxDripRate, maxBurst)
546 { 377 {
547 m_enabled = enabled; 378 m_enabled = enabled;
548 379
549 MaxDripRate = maxDripRate; 380 MaxDripRate = maxDripRate;
550 381
551 if (enabled) 382 if (enabled)
552 AdjustedDripRate = m_maxDripRate * .5f; 383 AdjustedDripRate = m_maxDripRate * .5f;
553 else 384 else
554 AdjustedDripRate = m_maxDripRate; 385 AdjustedDripRate = m_maxDripRate;
555>>>>>>> avn/ubitvar
556 } 386 }
557 387
558 /// <summary> 388 /// <summary>
559 /// Reliable packets sent to the client for which we never received an ack adjust the drip rate down. 389 /// Reliable packets sent to the client for which we never received an ack adjust the drip rate down.
560 /// <param name="packets">Number of packets that expired without successful delivery</param> 390 /// <param name="packets">Number of packets that expired without successful delivery</param>
561 /// </summary> 391 /// </summary>
562 public void ExpirePackets(Int32 packets) 392 public void ExpirePackets(Int32 count)
563 {
564 if (AdaptiveEnabled)
565 {
566 if (DebugLevel > 0)
567 m_log.WarnFormat(
568 "[ADAPTIVEBUCKET] drop {0} by {1} expired packets for {2}",
569 AdjustedDripRate, packets, Identifier);
570
571 // AdjustedDripRate = (Int64) (AdjustedDripRate / Math.Pow(2,packets));
572
573 // Compute the fallback solely on the rate allocated beyond the minimum, this
574 // should smooth out the fallback to the minimum rate
575 AdjustedDripRate = m_minimumFlow + (Int64) ((AdjustedDripRate - m_minimumFlow) / Math.Pow(2, packets));
576 }
577 }
578
579 /// <summary>
580 /// Reliable packets acked by the client adjust the drip rate up.
581 /// <param name="packets">Number of packets successfully acknowledged</param>
582 /// </summary>
583 public void AcknowledgePackets(Int32 packets)
584 { 393 {
585 if (AdaptiveEnabled) 394 // m_log.WarnFormat("[ADAPTIVEBUCKET] drop {0} by {1} expired packets",AdjustedDripRate,count);
586 AdjustedDripRate = AdjustedDripRate + packets * LLUDPServer.MTU; 395 if (m_enabled)
396 AdjustedDripRate = (Int64)(AdjustedDripRate / Math.Pow(2, count));
587 } 397 }
588 398
589 /// <summary> 399 // <summary>
590 /// Adjust the minimum flow level for the adaptive throttle, this will drop adjusted 400 //
591 /// throttles back to the minimum levels 401 // </summary>
592 /// <param>minDripRate--the new minimum flow</param> 402 public void AcknowledgePackets(Int32 count)
593 /// </summary>
594 public void ResetMinimumAdaptiveFlow(Int64 minDripRate)
595 { 403 {
596 m_minimumFlow = minDripRate; 404 if (m_enabled)
597 TargetDripRate = m_minimumFlow; 405 AdjustedDripRate = AdjustedDripRate + count;
598 AdjustedDripRate = m_minimumFlow;
599 } 406 }
600 } 407 }
601} 408}