diff --git a/Makefile b/Makefile index 43d0172..730e0b3 100644 --- a/Makefile +++ b/Makefile @@ -11,9 +11,6 @@ build: go mod download CGO_ENABLED=0 go build -tags release -ldflags $(LD_FLAGS) -o ctop -build-dev: - go build -ldflags "-w -X main.version=$(VERSION)-dev -X main.build=$(BUILD) -extldflags=$(EXT_LD_FLAGS)" - build-all: mkdir -p _build GOOS=darwin GOARCH=amd64 go build -tags release -ldflags $(LD_FLAGS) -o _build/ctop-$(VERSION)-darwin-amd64 @@ -23,6 +20,11 @@ build-all: GOOS=windows GOARCH=amd64 go build -tags release -ldflags $(LD_FLAGS) -o _build/ctop-$(VERSION)-windows-amd64 cd _build; sha256sum * > sha256sums.txt +run-dev: + rm -f ctop.sock ctop + go build -ldflags $(LD_FLAGS) -o ctop + CTOP_DEBUG=1 ./ctop + image: docker build -t ctop -f Dockerfile . diff --git a/container/main.go b/container/main.go index 416b878..88b3619 100644 --- a/container/main.go +++ b/container/main.go @@ -21,8 +21,8 @@ const ( type Container struct { models.Metrics Id string - Meta map[string]string - Widgets *compact.Compact + Meta models.Meta + Widgets *compact.CompactRow Display bool // display this container in compact view updater cwidgets.WidgetUpdater collector collector.Collector @@ -30,11 +30,11 @@ type Container struct { } func New(id string, collector collector.Collector, manager manager.Manager) *Container { - widgets := compact.NewCompact(id) + widgets := compact.NewCompactRow() return &Container{ Metrics: models.NewMetrics(), Id: id, - Meta: make(map[string]string), + Meta: models.NewMeta("id", id), Widgets: widgets, updater: widgets, collector: collector, @@ -44,21 +44,16 @@ func New(id string, collector collector.Collector, manager manager.Manager) *Con func (c *Container) SetUpdater(u cwidgets.WidgetUpdater) { c.updater = u - for k, v := range c.Meta { - c.updater.SetMeta(k, v) - } + c.updater.SetMeta(c.Meta) } func (c *Container) SetMeta(k, v string) { c.Meta[k] = v - c.updater.SetMeta(k, v) + c.updater.SetMeta(c.Meta) } func (c *Container) GetMeta(k string) string { - if v, ok := c.Meta[k]; ok { - return v - } - return "" + return c.Meta.Get(k) } func (c *Container) SetState(s string) { diff --git a/cwidgets/compact/gauge.go b/cwidgets/compact/gauge.go index 481fb32..fdf3a03 100644 --- a/cwidgets/compact/gauge.go +++ b/cwidgets/compact/gauge.go @@ -1,21 +1,59 @@ package compact import ( + "fmt" + + "github.com/bcicen/ctop/cwidgets" + "github.com/bcicen/ctop/models" ui "github.com/gizak/termui" ) -type GaugeCol struct { - *ui.Gauge +type CPUCol struct { + *GaugeCol } -func NewGaugeCol() *GaugeCol { - g := ui.NewGauge() +func NewCPUCol() CompactCol { + return &CPUCol{NewGaugeCol("CPU")} +} + +func (w *CPUCol) SetMetrics(m models.Metrics) { + val := m.CPUUtil + w.BarColor = colorScale(val) + w.Label = fmt.Sprintf("%d%%", val) + + if val > 100 { + val = 100 + } + w.Percent = val +} + +type MemCol struct { + *GaugeCol +} + +func NewMemCol() CompactCol { + return &MemCol{NewGaugeCol("MEM")} +} + +func (w *MemCol) SetMetrics(m models.Metrics) { + w.BarColor = ui.ThemeAttr("gauge.bar.bg") + w.Label = fmt.Sprintf("%s / %s", cwidgets.ByteFormat(m.MemUsage), cwidgets.ByteFormat(m.MemLimit)) + w.Percent = m.MemPercent +} + +type GaugeCol struct { + *ui.Gauge + header string + fWidth int +} + +func NewGaugeCol(header string) *GaugeCol { + g := &GaugeCol{ui.NewGauge(), header, 0} g.Height = 1 g.Border = false - g.Percent = 0 g.PaddingBottom = 0 - g.Label = "-" - return &GaugeCol{g} + g.Reset() + return g } func (w *GaugeCol) Reset() { @@ -23,11 +61,30 @@ func (w *GaugeCol) Reset() { w.Percent = 0 } +func (w *GaugeCol) Buffer() ui.Buffer { + // if bar would not otherwise be visible, set a minimum + // percentage value and low-contrast color for structure + if w.Percent < 5 { + w.Percent = 5 + w.BarColor = ui.ColorBlack + } + + return w.Gauge.Buffer() +} + +// GaugeCol implements CompactCol +func (w *GaugeCol) SetMeta(models.Meta) {} +func (w *GaugeCol) SetMetrics(models.Metrics) {} +func (w *GaugeCol) Header() string { return w.header } +func (w *GaugeCol) FixedWidth() int { return w.fWidth } + +// GaugeCol implements CompactCol func (w *GaugeCol) Highlight() { w.Bg = ui.ThemeAttr("par.text.fg") w.PercentColor = ui.ThemeAttr("par.text.hi") } +// GaugeCol implements CompactCol func (w *GaugeCol) UnHighlight() { w.Bg = ui.ThemeAttr("par.text.bg") w.PercentColor = ui.ThemeAttr("par.text.bg") diff --git a/cwidgets/compact/grid.go b/cwidgets/compact/grid.go index 8b483c2..710d7f2 100644 --- a/cwidgets/compact/grid.go +++ b/cwidgets/compact/grid.go @@ -4,11 +4,11 @@ import ( ui "github.com/gizak/termui" ) -var header *CompactHeader - type CompactGrid struct { ui.GridBufferer - Rows []ui.GridBufferer + header *CompactHeader + cols []CompactCol // reference columns + Rows []RowBufferer X, Y int Width int Height int @@ -16,8 +16,13 @@ type CompactGrid struct { } func NewCompactGrid() *CompactGrid { - header = NewCompactHeader() // init column header - return &CompactGrid{} + cg := &CompactGrid{header: NewCompactHeader()} + for _, wFn := range allCols { + w := wFn() + cg.cols = append(cg.cols, w) + cg.header.addFieldPar(w.Header()) + } + return cg } func (cg *CompactGrid) Align() { @@ -28,22 +33,47 @@ func (cg *CompactGrid) Align() { } // update row ypos, width recursively + colWidths := cg.calcWidths() for _, r := range cg.pageRows() { r.SetY(y) y += r.GetHeight() - r.SetWidth(cg.Width) + r.SetWidths(cg.Width, colWidths) } } -func (cg *CompactGrid) Clear() { cg.Rows = []ui.GridBufferer{} } -func (cg *CompactGrid) GetHeight() int { return len(cg.Rows) + header.Height } +func (cg *CompactGrid) Clear() { cg.Rows = []RowBufferer{} } +func (cg *CompactGrid) GetHeight() int { return len(cg.Rows) + cg.header.Height } func (cg *CompactGrid) SetX(x int) { cg.X = x } func (cg *CompactGrid) SetY(y int) { cg.Y = y } func (cg *CompactGrid) SetWidth(w int) { cg.Width = w } -func (cg *CompactGrid) MaxRows() int { return ui.TermHeight() - header.Height - cg.Y } +func (cg *CompactGrid) MaxRows() int { return ui.TermHeight() - cg.header.Height - cg.Y } -func (cg *CompactGrid) pageRows() (rows []ui.GridBufferer) { - rows = append(rows, header) +// calculate and return per-column width +func (cg *CompactGrid) calcWidths() []int { + var autoCols int + width := cg.Width + colWidths := make([]int, len(cg.cols)) + + for n, w := range cg.cols { + colWidths[n] = w.FixedWidth() + width -= w.FixedWidth() + if w.FixedWidth() == 0 { + autoCols++ + } + } + + spacing := colSpacing * len(cg.cols) + autoWidth := (width - spacing) / autoCols + for n, val := range colWidths { + if val == 0 { + colWidths[n] = autoWidth + } + } + return colWidths +} + +func (cg *CompactGrid) pageRows() (rows []RowBufferer) { + rows = append(rows, cg.header) rows = append(rows, cg.Rows[cg.Offset:]...) return rows } @@ -56,6 +86,6 @@ func (cg *CompactGrid) Buffer() ui.Buffer { return buf } -func (cg *CompactGrid) AddRows(rows ...ui.GridBufferer) { +func (cg *CompactGrid) AddRows(rows ...RowBufferer) { cg.Rows = append(cg.Rows, rows...) } diff --git a/cwidgets/compact/header.go b/cwidgets/compact/header.go index 6dbd298..2d5ecc9 100644 --- a/cwidgets/compact/header.go +++ b/cwidgets/compact/header.go @@ -8,63 +8,52 @@ type CompactHeader struct { X, Y int Width int Height int + cols []CompactCol + widths []int pars []*ui.Par } func NewCompactHeader() *CompactHeader { - fields := []string{"", "NAME", "CID", "CPU", "MEM", "NET RX/TX", "IO R/W", "PIDS"} - ch := &CompactHeader{} - ch.Height = 2 - for _, f := range fields { - ch.addFieldPar(f) + return &CompactHeader{Height: 2} +} + +func (row *CompactHeader) GetHeight() int { + return row.Height +} + +func (row *CompactHeader) SetWidths(totalWidth int, widths []int) { + x := row.X + + for n, w := range row.pars { + w.SetX(x) + w.SetWidth(widths[n]) + x += widths[n] + colSpacing } - return ch + row.Width = totalWidth } -func (ch *CompactHeader) GetHeight() int { - return ch.Height +func (row *CompactHeader) SetX(x int) { + row.X = x } -func (ch *CompactHeader) SetWidth(w int) { - x := ch.X - autoWidth := calcWidth(w) - for n, col := range ch.pars { - // set column to static width - if colWidths[n] != 0 { - col.SetX(x) - col.SetWidth(colWidths[n]) - x += colWidths[n] - continue - } - col.SetX(x) - col.SetWidth(autoWidth) - x += autoWidth + colSpacing - } - ch.Width = w -} - -func (ch *CompactHeader) SetX(x int) { - ch.X = x -} - -func (ch *CompactHeader) SetY(y int) { - for _, p := range ch.pars { +func (row *CompactHeader) SetY(y int) { + for _, p := range row.pars { p.SetY(y) } - ch.Y = y + row.Y = y } -func (ch *CompactHeader) Buffer() ui.Buffer { +func (row *CompactHeader) Buffer() ui.Buffer { buf := ui.NewBuffer() - for _, p := range ch.pars { + for _, p := range row.pars { buf.Merge(p.Buffer()) } return buf } -func (ch *CompactHeader) addFieldPar(s string) { +func (row *CompactHeader) addFieldPar(s string) { p := ui.NewPar(s) - p.Height = ch.Height + p.Height = row.Height p.Border = false - ch.pars = append(ch.pars, p) + row.pars = append(row.pars, p) } diff --git a/cwidgets/compact/main.go b/cwidgets/compact/main.go deleted file mode 100644 index af948fc..0000000 --- a/cwidgets/compact/main.go +++ /dev/null @@ -1,195 +0,0 @@ -package compact - -import ( - "github.com/bcicen/ctop/config" - "github.com/bcicen/ctop/logging" - "github.com/bcicen/ctop/models" - ui "github.com/gizak/termui" -) - -var log = logging.Init() - -type Compact struct { - Status *Status - Name *TextCol - Cid *TextCol - Cpu *GaugeCol - Mem *GaugeCol - Net *TextCol - IO *TextCol - Pids *TextCol - Bg *RowBg - X, Y int - Width int - Height int -} - -func NewCompact(id string) *Compact { - // truncate container id - if len(id) > 12 { - id = id[:12] - } - row := &Compact{ - Status: NewStatus(), - Name: NewTextCol("-"), - Cid: NewTextCol(id), - Cpu: NewGaugeCol(), - Mem: NewGaugeCol(), - Net: NewTextCol("-"), - IO: NewTextCol("-"), - Pids: NewTextCol("-"), - Bg: NewRowBg(), - X: 1, - Height: 1, - } - return row -} - -//func (row *Compact) ToggleExpand() { -//if row.Height == 1 { -//row.Height = 4 -//} else { -//row.Height = 1 -//} -//} - -func (row *Compact) SetMeta(k, v string) { - switch k { - case "name": - row.Name.Set(v) - case "state": - row.Status.Set(v) - case "health": - row.Status.SetHealth(v) - } -} - -func (row *Compact) SetMetrics(m models.Metrics) { - row.SetCPU(m.CPUUtil) - row.SetNet(m.NetRx, m.NetTx) - row.SetMem(m.MemUsage, m.MemLimit, m.MemPercent) - row.SetIO(m.IOBytesRead, m.IOBytesWrite) - row.SetPids(m.Pids) -} - -// Set gauges, counters to default unread values -func (row *Compact) Reset() { - row.Cpu.Reset() - row.Mem.Reset() - row.Net.Reset() - row.IO.Reset() - row.Pids.Reset() -} - -func (row *Compact) GetHeight() int { - return row.Height -} - -func (row *Compact) SetX(x int) { - row.X = x -} - -func (row *Compact) SetY(y int) { - if y == row.Y { - return - } - - row.Bg.Y = y - for _, col := range row.all() { - col.SetY(y) - } - row.Y = y -} - -func (row *Compact) SetWidth(width int) { - if width == row.Width { - return - } - x := row.X - - row.Bg.SetX(x + colWidths[0] + 1) - row.Bg.SetWidth(width) - - autoWidth := calcWidth(width) - for n, col := range row.all() { - if colWidths[n] != 0 { - col.SetX(x) - col.SetWidth(colWidths[n]) - x += colWidths[n] - continue - } - col.SetX(x) - col.SetWidth(autoWidth) - x += autoWidth + colSpacing - } - row.Width = width -} - -func (row *Compact) Buffer() ui.Buffer { - buf := ui.NewBuffer() - - buf.Merge(row.Bg.Buffer()) - buf.Merge(row.Status.Buffer()) - buf.Merge(row.Name.Buffer()) - buf.Merge(row.Cid.Buffer()) - buf.Merge(row.Cpu.Buffer()) - buf.Merge(row.Mem.Buffer()) - buf.Merge(row.Net.Buffer()) - buf.Merge(row.IO.Buffer()) - buf.Merge(row.Pids.Buffer()) - return buf -} - -func (row *Compact) all() []ui.GridBufferer { - return []ui.GridBufferer{ - row.Status, - row.Name, - row.Cid, - row.Cpu, - row.Mem, - row.Net, - row.IO, - row.Pids, - } -} - -func (row *Compact) Highlight() { - row.Name.Highlight() - if config.GetSwitchVal("fullRowCursor") { - row.Bg.Highlight() - row.Cid.Highlight() - row.Cpu.Highlight() - row.Mem.Highlight() - row.Net.Highlight() - row.IO.Highlight() - row.Pids.Highlight() - } -} - -func (row *Compact) UnHighlight() { - row.Name.UnHighlight() - if config.GetSwitchVal("fullRowCursor") { - row.Bg.UnHighlight() - row.Cid.UnHighlight() - row.Cpu.UnHighlight() - row.Mem.UnHighlight() - row.Net.UnHighlight() - row.IO.UnHighlight() - row.Pids.UnHighlight() - } -} - -type RowBg struct { - *ui.Par -} - -func NewRowBg() *RowBg { - bg := ui.NewPar("") - bg.Height = 1 - bg.Border = false - bg.Bg = ui.ThemeAttr("par.text.bg") - return &RowBg{bg} -} - -func (w *RowBg) Highlight() { w.Bg = ui.ThemeAttr("par.text.fg") } -func (w *RowBg) UnHighlight() { w.Bg = ui.ThemeAttr("par.text.bg") } diff --git a/cwidgets/compact/setters.go b/cwidgets/compact/setters.go deleted file mode 100644 index 779991e..0000000 --- a/cwidgets/compact/setters.go +++ /dev/null @@ -1,48 +0,0 @@ -package compact - -import ( - "fmt" - "strconv" - - "github.com/bcicen/ctop/cwidgets" - ui "github.com/gizak/termui" -) - -func (row *Compact) SetNet(rx int64, tx int64) { - label := fmt.Sprintf("%s / %s", cwidgets.ByteFormat(rx), cwidgets.ByteFormat(tx)) - row.Net.Set(label) -} - -func (row *Compact) SetIO(read int64, write int64) { - label := fmt.Sprintf("%s / %s", cwidgets.ByteFormat(read), cwidgets.ByteFormat(write)) - row.IO.Set(label) -} - -func (row *Compact) SetPids(val int) { - label := strconv.Itoa(val) - row.Pids.Set(label) -} - -func (row *Compact) SetCPU(val int) { - row.Cpu.BarColor = colorScale(val) - row.Cpu.Label = fmt.Sprintf("%s%%", strconv.Itoa(val)) - if val < 5 { - val = 5 - row.Cpu.BarColor = ui.ThemeAttr("gauge.bar.bg") - } - if val > 100 { - val = 100 - } - row.Cpu.Percent = val -} - -func (row *Compact) SetMem(val int64, limit int64, percent int) { - row.Mem.Label = fmt.Sprintf("%s / %s", cwidgets.ByteFormat(val), cwidgets.ByteFormat(limit)) - if percent < 5 { - percent = 5 - row.Mem.BarColor = ui.ColorBlack - } else { - row.Mem.BarColor = ui.ThemeAttr("gauge.bar.bg") - } - row.Mem.Percent = percent -} diff --git a/cwidgets/compact/status.go b/cwidgets/compact/status.go index d6b920f..fae7bdc 100644 --- a/cwidgets/compact/status.go +++ b/cwidgets/compact/status.go @@ -1,6 +1,7 @@ package compact import ( + "github.com/bcicen/ctop/models" ui "github.com/gizak/termui" ) @@ -17,14 +18,14 @@ type Status struct { health []ui.Cell } -func NewStatus() *Status { +func NewStatus() CompactCol { s := &Status{ Block: ui.NewBlock(), health: []ui.Cell{{Ch: ' '}}, } s.Height = 1 s.Border = false - s.Set("") + s.setState("") return s } @@ -43,7 +44,20 @@ func (s *Status) Buffer() ui.Buffer { return buf } -func (s *Status) Set(val string) { +func (s *Status) SetMeta(m models.Meta) { + s.setState(m.Get("state")) + s.setHealth(m.Get("health")) +} + +// Status implements CompactCol +func (s *Status) Reset() {} +func (s *Status) SetMetrics(models.Metrics) {} +func (s *Status) Highlight() {} +func (s *Status) UnHighlight() {} +func (s *Status) Header() string { return "" } +func (s *Status) FixedWidth() int { return 3 } + +func (s *Status) setState(val string) { // defaults text := mark color := ui.ColorDefault @@ -60,21 +74,21 @@ func (s *Status) Set(val string) { s.status = ui.TextCells(text, color, ui.ColorDefault) } -func (s *Status) SetHealth(val string) { - if val == "" { - return - } - +func (s *Status) setHealth(val string) { color := ui.ColorDefault mark := healthMark switch val { + case "": + return case "healthy": color = ui.ThemeAttr("status.ok") case "unhealthy": color = ui.ThemeAttr("status.danger") case "starting": color = ui.ThemeAttr("status.warn") + default: + log.Warningf("unknown health state string: \"%v\"", val) } s.health = ui.TextCells(mark, color, ui.ColorDefault) diff --git a/cwidgets/compact/text.go b/cwidgets/compact/text.go index e635e25..3ff9440 100644 --- a/cwidgets/compact/text.go +++ b/cwidgets/compact/text.go @@ -1,19 +1,93 @@ package compact import ( + "fmt" + + "github.com/bcicen/ctop/cwidgets" + "github.com/bcicen/ctop/models" ui "github.com/gizak/termui" ) -type TextCol struct { - *ui.Par +type NameCol struct { + *TextCol } -func NewTextCol(s string) *TextCol { - p := ui.NewPar(s) +func NewNameCol() CompactCol { + return &NameCol{NewTextCol("NAME")} +} + +func (w *NameCol) SetMeta(m models.Meta) { + w.Text = m.Get("name") + // truncate container id + if len(w.Text) > 12 { + w.Text = w.Text[:12] + } +} + +type CIDCol struct { + *TextCol +} + +func NewCIDCol() CompactCol { + return &CIDCol{NewTextCol("CID")} +} + +func (w *CIDCol) SetMeta(m models.Meta) { + w.Text = m.Get("id") +} + +type NetCol struct { + *TextCol +} + +func NewNetCol() CompactCol { + return &NetCol{NewTextCol("NET RX/TX")} +} + +func (w *NetCol) SetMetrics(m models.Metrics) { + label := fmt.Sprintf("%s / %s", cwidgets.ByteFormat(m.NetRx), cwidgets.ByteFormat(m.NetTx)) + w.Text = label +} + +type IOCol struct { + *TextCol +} + +func NewIOCol() CompactCol { + return &IOCol{NewTextCol("IO R/W")} +} + +func (w *IOCol) SetMetrics(m models.Metrics) { + label := fmt.Sprintf("%s / %s", cwidgets.ByteFormat(m.IOBytesRead), cwidgets.ByteFormat(m.IOBytesWrite)) + w.Text = label +} + +type PIDCol struct { + *TextCol +} + +func NewPIDCol() CompactCol { + w := &PIDCol{NewTextCol("PIDS")} + w.fWidth = 4 + return w +} + +func (w *PIDCol) SetMetrics(m models.Metrics) { + w.Text = fmt.Sprintf("%d", m.Pids) +} + +type TextCol struct { + *ui.Par + header string + fWidth int +} + +func NewTextCol(header string) *TextCol { + p := ui.NewPar("-") p.Border = false p.Height = 1 p.Width = 20 - return &TextCol{p} + return &TextCol{p, header, 0} } func (w *TextCol) Highlight() { @@ -28,10 +102,8 @@ func (w *TextCol) UnHighlight() { w.TextBgColor = ui.ThemeAttr("par.text.bg") } -func (w *TextCol) Reset() { - w.Text = "-" -} - -func (w *TextCol) Set(s string) { - w.Text = s -} +func (w *TextCol) Reset() { w.Text = "-" } +func (w *TextCol) SetMeta(models.Meta) {} +func (w *TextCol) SetMetrics(models.Metrics) {} +func (w *TextCol) Header() string { return w.header } +func (w *TextCol) FixedWidth() int { return w.fWidth } diff --git a/cwidgets/compact/util.go b/cwidgets/compact/util.go index 9bd8f66..ec9b149 100644 --- a/cwidgets/compact/util.go +++ b/cwidgets/compact/util.go @@ -22,19 +22,6 @@ var colWidths = []int{ 4, // pids } -// Calculate per-column width, given total width -func calcWidth(width int) int { - spacing := colSpacing * len(colWidths) - var staticCols int - for _, w := range colWidths { - width -= w - if w == 0 { - staticCols++ - } - } - return (width - spacing) / staticCols -} - func centerParText(p *ui.Par) { var text string var padding string diff --git a/cwidgets/main.go b/cwidgets/main.go index 50e4d2e..fd2f68b 100644 --- a/cwidgets/main.go +++ b/cwidgets/main.go @@ -8,6 +8,6 @@ import ( var log = logging.Init() type WidgetUpdater interface { - SetMeta(string, string) + SetMeta(models.Meta) SetMetrics(models.Metrics) } diff --git a/cwidgets/single/main.go b/cwidgets/single/main.go index 8140b53..6f7b02c 100644 --- a/cwidgets/single/main.go +++ b/cwidgets/single/main.go @@ -55,11 +55,13 @@ func (e *Single) Down() { } func (e *Single) SetWidth(w int) { e.Width = w } -func (e *Single) SetMeta(k, v string) { - if k == "[ENV-VAR]" { - e.Env.Set(k, v) - } else { - e.Info.Set(k, v) +func (e *Single) SetMeta(m models.Meta) { + for k, v := range m { + if k == "[ENV-VAR]" { + e.Env.Set(k, v) + } else { + e.Info.Set(k, v) + } } } diff --git a/models/main.go b/models/main.go index feb23ce..86168b2 100644 --- a/models/main.go +++ b/models/main.go @@ -7,6 +7,33 @@ type Log struct { Message string } +type Meta map[string]string + +// NewMeta returns an initialized Meta map. +// An optional series of key, values may be provided to populate the map prior to returning +func NewMeta(kvs ...string) Meta { + m := make(Meta) + + var k string + for i := 0; i < len(kvs)-1; i++ { + if k == "" { + k = kvs[i] + } else { + m[k] = kvs[i] + k = "" + } + } + + return m +} + +func (m Meta) Get(k string) string { + if s, ok := m[k]; ok { + return s + } + return "" +} + type Metrics struct { CPUUtil int NetTx int64