1package netlink 2 3import ( 4 "fmt" 5 "io/ioutil" 6 "strconv" 7 "strings" 8 "syscall" 9 10 "github.com/vishvananda/netlink/nl" 11 "golang.org/x/sys/unix" 12) 13 14// NOTE function is here because it uses other linux functions 15func NewNetem(attrs QdiscAttrs, nattrs NetemQdiscAttrs) *Netem { 16 var limit uint32 = 1000 17 var lossCorr, delayCorr, duplicateCorr uint32 18 var reorderProb, reorderCorr uint32 19 var corruptProb, corruptCorr uint32 20 21 latency := nattrs.Latency 22 loss := Percentage2u32(nattrs.Loss) 23 gap := nattrs.Gap 24 duplicate := Percentage2u32(nattrs.Duplicate) 25 jitter := nattrs.Jitter 26 27 // Correlation 28 if latency > 0 && jitter > 0 { 29 delayCorr = Percentage2u32(nattrs.DelayCorr) 30 } 31 if loss > 0 { 32 lossCorr = Percentage2u32(nattrs.LossCorr) 33 } 34 if duplicate > 0 { 35 duplicateCorr = Percentage2u32(nattrs.DuplicateCorr) 36 } 37 // FIXME should validate values(like loss/duplicate are percentages...) 38 latency = time2Tick(latency) 39 40 if nattrs.Limit != 0 { 41 limit = nattrs.Limit 42 } 43 // Jitter is only value if latency is > 0 44 if latency > 0 { 45 jitter = time2Tick(jitter) 46 } 47 48 reorderProb = Percentage2u32(nattrs.ReorderProb) 49 reorderCorr = Percentage2u32(nattrs.ReorderCorr) 50 51 if reorderProb > 0 { 52 // ERROR if lantency == 0 53 if gap == 0 { 54 gap = 1 55 } 56 } 57 58 corruptProb = Percentage2u32(nattrs.CorruptProb) 59 corruptCorr = Percentage2u32(nattrs.CorruptCorr) 60 61 return &Netem{ 62 QdiscAttrs: attrs, 63 Latency: latency, 64 DelayCorr: delayCorr, 65 Limit: limit, 66 Loss: loss, 67 LossCorr: lossCorr, 68 Gap: gap, 69 Duplicate: duplicate, 70 DuplicateCorr: duplicateCorr, 71 Jitter: jitter, 72 ReorderProb: reorderProb, 73 ReorderCorr: reorderCorr, 74 CorruptProb: corruptProb, 75 CorruptCorr: corruptCorr, 76 } 77} 78 79// QdiscDel will delete a qdisc from the system. 80// Equivalent to: `tc qdisc del $qdisc` 81func QdiscDel(qdisc Qdisc) error { 82 return pkgHandle.QdiscDel(qdisc) 83} 84 85// QdiscDel will delete a qdisc from the system. 86// Equivalent to: `tc qdisc del $qdisc` 87func (h *Handle) QdiscDel(qdisc Qdisc) error { 88 return h.qdiscModify(unix.RTM_DELQDISC, 0, qdisc) 89} 90 91// QdiscChange will change a qdisc in place 92// Equivalent to: `tc qdisc change $qdisc` 93// The parent and handle MUST NOT be changed. 94func QdiscChange(qdisc Qdisc) error { 95 return pkgHandle.QdiscChange(qdisc) 96} 97 98// QdiscChange will change a qdisc in place 99// Equivalent to: `tc qdisc change $qdisc` 100// The parent and handle MUST NOT be changed. 101func (h *Handle) QdiscChange(qdisc Qdisc) error { 102 return h.qdiscModify(unix.RTM_NEWQDISC, 0, qdisc) 103} 104 105// QdiscReplace will replace a qdisc to the system. 106// Equivalent to: `tc qdisc replace $qdisc` 107// The handle MUST change. 108func QdiscReplace(qdisc Qdisc) error { 109 return pkgHandle.QdiscReplace(qdisc) 110} 111 112// QdiscReplace will replace a qdisc to the system. 113// Equivalent to: `tc qdisc replace $qdisc` 114// The handle MUST change. 115func (h *Handle) QdiscReplace(qdisc Qdisc) error { 116 return h.qdiscModify( 117 unix.RTM_NEWQDISC, 118 unix.NLM_F_CREATE|unix.NLM_F_REPLACE, 119 qdisc) 120} 121 122// QdiscAdd will add a qdisc to the system. 123// Equivalent to: `tc qdisc add $qdisc` 124func QdiscAdd(qdisc Qdisc) error { 125 return pkgHandle.QdiscAdd(qdisc) 126} 127 128// QdiscAdd will add a qdisc to the system. 129// Equivalent to: `tc qdisc add $qdisc` 130func (h *Handle) QdiscAdd(qdisc Qdisc) error { 131 return h.qdiscModify( 132 unix.RTM_NEWQDISC, 133 unix.NLM_F_CREATE|unix.NLM_F_EXCL, 134 qdisc) 135} 136 137func (h *Handle) qdiscModify(cmd, flags int, qdisc Qdisc) error { 138 req := h.newNetlinkRequest(cmd, flags|unix.NLM_F_ACK) 139 base := qdisc.Attrs() 140 msg := &nl.TcMsg{ 141 Family: nl.FAMILY_ALL, 142 Ifindex: int32(base.LinkIndex), 143 Handle: base.Handle, 144 Parent: base.Parent, 145 } 146 req.AddData(msg) 147 148 // When deleting don't bother building the rest of the netlink payload 149 if cmd != unix.RTM_DELQDISC { 150 if err := qdiscPayload(req, qdisc); err != nil { 151 return err 152 } 153 } 154 155 _, err := req.Execute(unix.NETLINK_ROUTE, 0) 156 return err 157} 158 159func qdiscPayload(req *nl.NetlinkRequest, qdisc Qdisc) error { 160 161 req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(qdisc.Type()))) 162 163 options := nl.NewRtAttr(nl.TCA_OPTIONS, nil) 164 165 switch qdisc := qdisc.(type) { 166 case *Prio: 167 tcmap := nl.TcPrioMap{ 168 Bands: int32(qdisc.Bands), 169 Priomap: qdisc.PriorityMap, 170 } 171 options = nl.NewRtAttr(nl.TCA_OPTIONS, tcmap.Serialize()) 172 case *Tbf: 173 opt := nl.TcTbfQopt{} 174 opt.Rate.Rate = uint32(qdisc.Rate) 175 opt.Peakrate.Rate = uint32(qdisc.Peakrate) 176 opt.Limit = qdisc.Limit 177 opt.Buffer = qdisc.Buffer 178 options.AddRtAttr(nl.TCA_TBF_PARMS, opt.Serialize()) 179 if qdisc.Rate >= uint64(1<<32) { 180 options.AddRtAttr(nl.TCA_TBF_RATE64, nl.Uint64Attr(qdisc.Rate)) 181 } 182 if qdisc.Peakrate >= uint64(1<<32) { 183 options.AddRtAttr(nl.TCA_TBF_PRATE64, nl.Uint64Attr(qdisc.Peakrate)) 184 } 185 if qdisc.Peakrate > 0 { 186 options.AddRtAttr(nl.TCA_TBF_PBURST, nl.Uint32Attr(qdisc.Minburst)) 187 } 188 case *Htb: 189 opt := nl.TcHtbGlob{} 190 opt.Version = qdisc.Version 191 opt.Rate2Quantum = qdisc.Rate2Quantum 192 opt.Defcls = qdisc.Defcls 193 // TODO: Handle Debug properly. For now default to 0 194 opt.Debug = qdisc.Debug 195 opt.DirectPkts = qdisc.DirectPkts 196 options.AddRtAttr(nl.TCA_HTB_INIT, opt.Serialize()) 197 // options.AddRtAttr(nl.TCA_HTB_DIRECT_QLEN, opt.Serialize()) 198 case *Hfsc: 199 opt := nl.TcHfscOpt{} 200 opt.Defcls = qdisc.Defcls 201 options = nl.NewRtAttr(nl.TCA_OPTIONS, opt.Serialize()) 202 case *Netem: 203 opt := nl.TcNetemQopt{} 204 opt.Latency = qdisc.Latency 205 opt.Limit = qdisc.Limit 206 opt.Loss = qdisc.Loss 207 opt.Gap = qdisc.Gap 208 opt.Duplicate = qdisc.Duplicate 209 opt.Jitter = qdisc.Jitter 210 options = nl.NewRtAttr(nl.TCA_OPTIONS, opt.Serialize()) 211 // Correlation 212 corr := nl.TcNetemCorr{} 213 corr.DelayCorr = qdisc.DelayCorr 214 corr.LossCorr = qdisc.LossCorr 215 corr.DupCorr = qdisc.DuplicateCorr 216 217 if corr.DelayCorr > 0 || corr.LossCorr > 0 || corr.DupCorr > 0 { 218 options.AddRtAttr(nl.TCA_NETEM_CORR, corr.Serialize()) 219 } 220 // Corruption 221 corruption := nl.TcNetemCorrupt{} 222 corruption.Probability = qdisc.CorruptProb 223 corruption.Correlation = qdisc.CorruptCorr 224 if corruption.Probability > 0 { 225 options.AddRtAttr(nl.TCA_NETEM_CORRUPT, corruption.Serialize()) 226 } 227 // Reorder 228 reorder := nl.TcNetemReorder{} 229 reorder.Probability = qdisc.ReorderProb 230 reorder.Correlation = qdisc.ReorderCorr 231 if reorder.Probability > 0 { 232 options.AddRtAttr(nl.TCA_NETEM_REORDER, reorder.Serialize()) 233 } 234 case *Ingress: 235 // ingress filters must use the proper handle 236 if qdisc.Attrs().Parent != HANDLE_INGRESS { 237 return fmt.Errorf("Ingress filters must set Parent to HANDLE_INGRESS") 238 } 239 case *FqCodel: 240 options.AddRtAttr(nl.TCA_FQ_CODEL_ECN, nl.Uint32Attr((uint32(qdisc.ECN)))) 241 if qdisc.Limit > 0 { 242 options.AddRtAttr(nl.TCA_FQ_CODEL_LIMIT, nl.Uint32Attr((uint32(qdisc.Limit)))) 243 } 244 if qdisc.Interval > 0 { 245 options.AddRtAttr(nl.TCA_FQ_CODEL_INTERVAL, nl.Uint32Attr((uint32(qdisc.Interval)))) 246 } 247 if qdisc.Flows > 0 { 248 options.AddRtAttr(nl.TCA_FQ_CODEL_FLOWS, nl.Uint32Attr((uint32(qdisc.Flows)))) 249 } 250 if qdisc.Quantum > 0 { 251 options.AddRtAttr(nl.TCA_FQ_CODEL_QUANTUM, nl.Uint32Attr((uint32(qdisc.Quantum)))) 252 } 253 254 case *Fq: 255 options.AddRtAttr(nl.TCA_FQ_RATE_ENABLE, nl.Uint32Attr((uint32(qdisc.Pacing)))) 256 257 if qdisc.Buckets > 0 { 258 options.AddRtAttr(nl.TCA_FQ_BUCKETS_LOG, nl.Uint32Attr((uint32(qdisc.Buckets)))) 259 } 260 if qdisc.LowRateThreshold > 0 { 261 options.AddRtAttr(nl.TCA_FQ_LOW_RATE_THRESHOLD, nl.Uint32Attr((uint32(qdisc.LowRateThreshold)))) 262 } 263 if qdisc.Quantum > 0 { 264 options.AddRtAttr(nl.TCA_FQ_QUANTUM, nl.Uint32Attr((uint32(qdisc.Quantum)))) 265 } 266 if qdisc.InitialQuantum > 0 { 267 options.AddRtAttr(nl.TCA_FQ_INITIAL_QUANTUM, nl.Uint32Attr((uint32(qdisc.InitialQuantum)))) 268 } 269 if qdisc.FlowRefillDelay > 0 { 270 options.AddRtAttr(nl.TCA_FQ_FLOW_REFILL_DELAY, nl.Uint32Attr((uint32(qdisc.FlowRefillDelay)))) 271 } 272 if qdisc.FlowPacketLimit > 0 { 273 options.AddRtAttr(nl.TCA_FQ_FLOW_PLIMIT, nl.Uint32Attr((uint32(qdisc.FlowPacketLimit)))) 274 } 275 if qdisc.FlowMaxRate > 0 { 276 options.AddRtAttr(nl.TCA_FQ_FLOW_MAX_RATE, nl.Uint32Attr((uint32(qdisc.FlowMaxRate)))) 277 } 278 if qdisc.FlowDefaultRate > 0 { 279 options.AddRtAttr(nl.TCA_FQ_FLOW_DEFAULT_RATE, nl.Uint32Attr((uint32(qdisc.FlowDefaultRate)))) 280 } 281 default: 282 options = nil 283 } 284 285 if options != nil { 286 req.AddData(options) 287 } 288 return nil 289} 290 291// QdiscList gets a list of qdiscs in the system. 292// Equivalent to: `tc qdisc show`. 293// The list can be filtered by link. 294func QdiscList(link Link) ([]Qdisc, error) { 295 return pkgHandle.QdiscList(link) 296} 297 298// QdiscList gets a list of qdiscs in the system. 299// Equivalent to: `tc qdisc show`. 300// The list can be filtered by link. 301func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { 302 req := h.newNetlinkRequest(unix.RTM_GETQDISC, unix.NLM_F_DUMP) 303 index := int32(0) 304 if link != nil { 305 base := link.Attrs() 306 h.ensureIndex(base) 307 index = int32(base.Index) 308 } 309 msg := &nl.TcMsg{ 310 Family: nl.FAMILY_ALL, 311 Ifindex: index, 312 } 313 req.AddData(msg) 314 315 msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWQDISC) 316 if err != nil { 317 return nil, err 318 } 319 320 var res []Qdisc 321 for _, m := range msgs { 322 msg := nl.DeserializeTcMsg(m) 323 324 attrs, err := nl.ParseRouteAttr(m[msg.Len():]) 325 if err != nil { 326 return nil, err 327 } 328 329 // skip qdiscs from other interfaces 330 if link != nil && msg.Ifindex != index { 331 continue 332 } 333 334 base := QdiscAttrs{ 335 LinkIndex: int(msg.Ifindex), 336 Handle: msg.Handle, 337 Parent: msg.Parent, 338 Refcnt: msg.Info, 339 } 340 var qdisc Qdisc 341 qdiscType := "" 342 for _, attr := range attrs { 343 switch attr.Attr.Type { 344 case nl.TCA_KIND: 345 qdiscType = string(attr.Value[:len(attr.Value)-1]) 346 switch qdiscType { 347 case "pfifo_fast": 348 qdisc = &PfifoFast{} 349 case "prio": 350 qdisc = &Prio{} 351 case "tbf": 352 qdisc = &Tbf{} 353 case "ingress": 354 qdisc = &Ingress{} 355 case "htb": 356 qdisc = &Htb{} 357 case "fq": 358 qdisc = &Fq{} 359 case "hfsc": 360 qdisc = &Hfsc{} 361 case "fq_codel": 362 qdisc = &FqCodel{} 363 case "netem": 364 qdisc = &Netem{} 365 default: 366 qdisc = &GenericQdisc{QdiscType: qdiscType} 367 } 368 case nl.TCA_OPTIONS: 369 switch qdiscType { 370 case "pfifo_fast": 371 // pfifo returns TcPrioMap directly without wrapping it in rtattr 372 if err := parsePfifoFastData(qdisc, attr.Value); err != nil { 373 return nil, err 374 } 375 case "prio": 376 // prio returns TcPrioMap directly without wrapping it in rtattr 377 if err := parsePrioData(qdisc, attr.Value); err != nil { 378 return nil, err 379 } 380 case "tbf": 381 data, err := nl.ParseRouteAttr(attr.Value) 382 if err != nil { 383 return nil, err 384 } 385 if err := parseTbfData(qdisc, data); err != nil { 386 return nil, err 387 } 388 case "hfsc": 389 if err := parseHfscData(qdisc, attr.Value); err != nil { 390 return nil, err 391 } 392 case "htb": 393 data, err := nl.ParseRouteAttr(attr.Value) 394 if err != nil { 395 return nil, err 396 } 397 if err := parseHtbData(qdisc, data); err != nil { 398 return nil, err 399 } 400 case "fq": 401 data, err := nl.ParseRouteAttr(attr.Value) 402 if err != nil { 403 return nil, err 404 } 405 if err := parseFqData(qdisc, data); err != nil { 406 return nil, err 407 } 408 case "fq_codel": 409 data, err := nl.ParseRouteAttr(attr.Value) 410 if err != nil { 411 return nil, err 412 } 413 if err := parseFqCodelData(qdisc, data); err != nil { 414 return nil, err 415 } 416 case "netem": 417 if err := parseNetemData(qdisc, attr.Value); err != nil { 418 return nil, err 419 } 420 421 // no options for ingress 422 } 423 } 424 } 425 *qdisc.Attrs() = base 426 res = append(res, qdisc) 427 } 428 429 return res, nil 430} 431 432func parsePfifoFastData(qdisc Qdisc, value []byte) error { 433 pfifo := qdisc.(*PfifoFast) 434 tcmap := nl.DeserializeTcPrioMap(value) 435 pfifo.PriorityMap = tcmap.Priomap 436 pfifo.Bands = uint8(tcmap.Bands) 437 return nil 438} 439 440func parsePrioData(qdisc Qdisc, value []byte) error { 441 prio := qdisc.(*Prio) 442 tcmap := nl.DeserializeTcPrioMap(value) 443 prio.PriorityMap = tcmap.Priomap 444 prio.Bands = uint8(tcmap.Bands) 445 return nil 446} 447 448func parseHtbData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { 449 native = nl.NativeEndian() 450 htb := qdisc.(*Htb) 451 for _, datum := range data { 452 switch datum.Attr.Type { 453 case nl.TCA_HTB_INIT: 454 opt := nl.DeserializeTcHtbGlob(datum.Value) 455 htb.Version = opt.Version 456 htb.Rate2Quantum = opt.Rate2Quantum 457 htb.Defcls = opt.Defcls 458 htb.Debug = opt.Debug 459 htb.DirectPkts = opt.DirectPkts 460 case nl.TCA_HTB_DIRECT_QLEN: 461 // TODO 462 //htb.DirectQlen = native.uint32(datum.Value) 463 } 464 } 465 return nil 466} 467 468func parseFqCodelData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { 469 native = nl.NativeEndian() 470 fqCodel := qdisc.(*FqCodel) 471 for _, datum := range data { 472 473 switch datum.Attr.Type { 474 case nl.TCA_FQ_CODEL_TARGET: 475 fqCodel.Target = native.Uint32(datum.Value) 476 case nl.TCA_FQ_CODEL_LIMIT: 477 fqCodel.Limit = native.Uint32(datum.Value) 478 case nl.TCA_FQ_CODEL_INTERVAL: 479 fqCodel.Interval = native.Uint32(datum.Value) 480 case nl.TCA_FQ_CODEL_ECN: 481 fqCodel.ECN = native.Uint32(datum.Value) 482 case nl.TCA_FQ_CODEL_FLOWS: 483 fqCodel.Flows = native.Uint32(datum.Value) 484 case nl.TCA_FQ_CODEL_QUANTUM: 485 fqCodel.Quantum = native.Uint32(datum.Value) 486 } 487 } 488 return nil 489} 490 491func parseHfscData(qdisc Qdisc, data []byte) error { 492 Hfsc := qdisc.(*Hfsc) 493 native = nl.NativeEndian() 494 Hfsc.Defcls = native.Uint16(data) 495 return nil 496} 497 498func parseFqData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { 499 native = nl.NativeEndian() 500 fq := qdisc.(*Fq) 501 for _, datum := range data { 502 switch datum.Attr.Type { 503 case nl.TCA_FQ_BUCKETS_LOG: 504 fq.Buckets = native.Uint32(datum.Value) 505 case nl.TCA_FQ_LOW_RATE_THRESHOLD: 506 fq.LowRateThreshold = native.Uint32(datum.Value) 507 case nl.TCA_FQ_QUANTUM: 508 fq.Quantum = native.Uint32(datum.Value) 509 case nl.TCA_FQ_RATE_ENABLE: 510 fq.Pacing = native.Uint32(datum.Value) 511 case nl.TCA_FQ_INITIAL_QUANTUM: 512 fq.InitialQuantum = native.Uint32(datum.Value) 513 case nl.TCA_FQ_ORPHAN_MASK: 514 // TODO 515 case nl.TCA_FQ_FLOW_REFILL_DELAY: 516 fq.FlowRefillDelay = native.Uint32(datum.Value) 517 case nl.TCA_FQ_FLOW_PLIMIT: 518 fq.FlowPacketLimit = native.Uint32(datum.Value) 519 case nl.TCA_FQ_PLIMIT: 520 fq.PacketLimit = native.Uint32(datum.Value) 521 case nl.TCA_FQ_FLOW_MAX_RATE: 522 fq.FlowMaxRate = native.Uint32(datum.Value) 523 case nl.TCA_FQ_FLOW_DEFAULT_RATE: 524 fq.FlowDefaultRate = native.Uint32(datum.Value) 525 } 526 } 527 return nil 528} 529 530func parseNetemData(qdisc Qdisc, value []byte) error { 531 netem := qdisc.(*Netem) 532 opt := nl.DeserializeTcNetemQopt(value) 533 netem.Latency = opt.Latency 534 netem.Limit = opt.Limit 535 netem.Loss = opt.Loss 536 netem.Gap = opt.Gap 537 netem.Duplicate = opt.Duplicate 538 netem.Jitter = opt.Jitter 539 data, err := nl.ParseRouteAttr(value[nl.SizeofTcNetemQopt:]) 540 if err != nil { 541 return err 542 } 543 for _, datum := range data { 544 switch datum.Attr.Type { 545 case nl.TCA_NETEM_CORR: 546 opt := nl.DeserializeTcNetemCorr(datum.Value) 547 netem.DelayCorr = opt.DelayCorr 548 netem.LossCorr = opt.LossCorr 549 netem.DuplicateCorr = opt.DupCorr 550 case nl.TCA_NETEM_CORRUPT: 551 opt := nl.DeserializeTcNetemCorrupt(datum.Value) 552 netem.CorruptProb = opt.Probability 553 netem.CorruptCorr = opt.Correlation 554 case nl.TCA_NETEM_REORDER: 555 opt := nl.DeserializeTcNetemReorder(datum.Value) 556 netem.ReorderProb = opt.Probability 557 netem.ReorderCorr = opt.Correlation 558 } 559 } 560 return nil 561} 562 563func parseTbfData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { 564 native = nl.NativeEndian() 565 tbf := qdisc.(*Tbf) 566 for _, datum := range data { 567 switch datum.Attr.Type { 568 case nl.TCA_TBF_PARMS: 569 opt := nl.DeserializeTcTbfQopt(datum.Value) 570 tbf.Rate = uint64(opt.Rate.Rate) 571 tbf.Peakrate = uint64(opt.Peakrate.Rate) 572 tbf.Limit = opt.Limit 573 tbf.Buffer = opt.Buffer 574 case nl.TCA_TBF_RATE64: 575 tbf.Rate = native.Uint64(datum.Value[0:8]) 576 case nl.TCA_TBF_PRATE64: 577 tbf.Peakrate = native.Uint64(datum.Value[0:8]) 578 case nl.TCA_TBF_PBURST: 579 tbf.Minburst = native.Uint32(datum.Value[0:4]) 580 } 581 } 582 return nil 583} 584 585const ( 586 TIME_UNITS_PER_SEC = 1000000 587) 588 589var ( 590 tickInUsec float64 591 clockFactor float64 592 hz float64 593) 594 595func initClock() { 596 data, err := ioutil.ReadFile("/proc/net/psched") 597 if err != nil { 598 return 599 } 600 parts := strings.Split(strings.TrimSpace(string(data)), " ") 601 if len(parts) < 3 { 602 return 603 } 604 var vals [3]uint64 605 for i := range vals { 606 val, err := strconv.ParseUint(parts[i], 16, 32) 607 if err != nil { 608 return 609 } 610 vals[i] = val 611 } 612 // compatibility 613 if vals[2] == 1000000000 { 614 vals[0] = vals[1] 615 } 616 clockFactor = float64(vals[2]) / TIME_UNITS_PER_SEC 617 tickInUsec = float64(vals[0]) / float64(vals[1]) * clockFactor 618 hz = float64(vals[0]) 619} 620 621func TickInUsec() float64 { 622 if tickInUsec == 0.0 { 623 initClock() 624 } 625 return tickInUsec 626} 627 628func ClockFactor() float64 { 629 if clockFactor == 0.0 { 630 initClock() 631 } 632 return clockFactor 633} 634 635func Hz() float64 { 636 if hz == 0.0 { 637 initClock() 638 } 639 return hz 640} 641 642func time2Tick(time uint32) uint32 { 643 return uint32(float64(time) * TickInUsec()) 644} 645 646func tick2Time(tick uint32) uint32 { 647 return uint32(float64(tick) / TickInUsec()) 648} 649 650func time2Ktime(time uint32) uint32 { 651 return uint32(float64(time) * ClockFactor()) 652} 653 654func ktime2Time(ktime uint32) uint32 { 655 return uint32(float64(ktime) / ClockFactor()) 656} 657 658func burst(rate uint64, buffer uint32) uint32 { 659 return uint32(float64(rate) * float64(tick2Time(buffer)) / TIME_UNITS_PER_SEC) 660} 661 662func latency(rate uint64, limit, buffer uint32) float64 { 663 return TIME_UNITS_PER_SEC*(float64(limit)/float64(rate)) - float64(tick2Time(buffer)) 664} 665 666func Xmittime(rate uint64, size uint32) float64 { 667 return TickInUsec() * TIME_UNITS_PER_SEC * (float64(size) / float64(rate)) 668} 669