perfmonger 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fec4f6034a16fc8915547917510cb8c69299b095
4
- data.tar.gz: 6eb9b12b445f78adf0709d298ce71279abff2c8a
3
+ metadata.gz: 8e47b0d30a3300a01ae5efcc0bbbe10baf524561
4
+ data.tar.gz: 88988c5009fcbe62049c7355f3e8374b3c825f5d
5
5
  SHA512:
6
- metadata.gz: 101f22ee04ff2cdee38ddcae194970ce5af08c70a4850c295e38ecece1c14648bc1953b35558a9735f642423f03cac583b868010a9f35d8fd9a629d0ea9b1f5a
7
- data.tar.gz: 31321ba34ecc71ce31668f13e32d58f2d82fd2808dfbabb3c3d869ae78e70fba46020e3dbfe2779e8a2cd202e2c43e392ee1266a4a94db748b726949d7d8a34d
6
+ metadata.gz: 6f767fae2183025478897926d3b79d705fd30937f8c7f7cd6221dd29ad0d9ae03f7940c29be234613f8100f1c9dafb3e8d079075ab2d2d1879b2de33fdc2e482
7
+ data.tar.gz: e4f1d714cd58d57db382320224d356f919692f25a4552d5649accf7a27c63cac693049972dd994904cdb4d4a54c9f2c19e374b25d928f392e4bffbdb517d5460
data/.gitignore CHANGED
@@ -11,3 +11,6 @@
11
11
  /cpu.dat
12
12
  /disk.dat
13
13
  /perfmonger.pgr
14
+ /.wercker/
15
+ /spec/examples.txt
16
+ /perfmonger.pgr.gz
data/.travis.yml CHANGED
@@ -1,17 +1,18 @@
1
-
2
1
  language: ruby
3
2
  install:
4
3
  - sudo apt-get update
5
4
  - sudo apt-get install gnuplot
6
5
  - gnuplot -e "set terminal" < /dev/null 2>&1
7
6
  - bundle install
8
- - go_version="1.4.2"
7
+ - go_version="1.8.3"
9
8
  - wget http://golang.org/dl/go${go_version}.linux-amd64.tar.gz
10
9
  - sudo tar -C /usr/local -xzf go${go_version}.linux-amd64.tar.gz
11
10
  - export PATH=$PATH:/usr/local/go/bin
12
11
  - export GOPATH="$HOME/go"
13
12
  - export PATH="$PATH:$GOPATH/bin"
14
13
  - mkdir -p "$HOME/go/{src,pkg,bin}"
14
+ - go get -u github.com/mattn/go-isatty
15
+ - go get -u github.com/hayamiz/go-projson
15
16
  - go get -u golang.org/x/crypto/ssh/terminal
16
17
  - go get -u github.com/hayamiz/perfmonger/core/subsystem
17
18
  rvm:
@@ -22,3 +23,6 @@ rvm:
22
23
  script:
23
24
  - rake spec
24
25
  - rake test_core
26
+ notifications:
27
+ slack:
28
+ secure: fH8tRyxWHL60OV6QuJlzig9lCLbjfpHx8E6D2EzgQz7+/wqAxtoTUyiN7mbpEJa4hyQeZfpmMpDTnl2tHD6eI8yqjAsY4Q+jt21tCKyrKMegq9Pypd4eMP4o+DupT2mXm0K3cZ2Kgb+yP8AuJPoTy20j3kpmnDFWdqRnhgpBLC8=
data/HOWTO.md CHANGED
@@ -3,7 +3,6 @@
3
3
  ## TODO in release
4
4
 
5
5
  1. Make a commit for changing version number
6
- * Update version number in configure.ac
7
6
  * Add a new version number and release date to NEWS
8
7
  2. Tag the commit with name `v<VERSION>` ex) `v0.3.0`
9
8
  3. Push tags to the github repository
data/NEWS CHANGED
@@ -1,10 +1,19 @@
1
- ## 2017-09-14: PerfMonger 0.10.2
1
+ ## 2018-05-15: PerfMonger 0.11.0
2
+ * New features
3
+ * [play] subcommand:
4
+ * Add --color, --pretty option for pretty JSON output
5
+ * [live] subcommand:
6
+ * Add --color, --pretty option for pretty JSON output
7
+ * [fingerprint] subcommand:
8
+ * Collect additional info: numactl, ec2-metadata
2
9
  * Bug fixes
3
10
  * [plot] subcommand:
4
- * Delete temporary files after plot
5
-
6
- ## 2017-09-08: PerfMonger 0.10.1
7
- * Yanked wrongly shipped v0.10.0 and pushed up-to-date code
11
+ * Correctly filter out disk usages by --disk-only option
12
+ * Changes
13
+ * Dropped support of i386
14
+ * Use go-projson for JSON output
15
+ * [plot] subcommand:
16
+ * Stacked graphs layout in allcpu.pdf
8
17
 
9
18
  ## 2017-06-19: PerfMonger 0.10.0
10
19
  * New features
@@ -204,4 +213,4 @@ PerfMonger is available on yum repository from this release.
204
213
  ## 2011-12-09: PerfMonger 0.1.0 released
205
214
 
206
215
  * New features
207
- * I/O performance monitoring
216
+ * I/O performance monitoring
data/README.md CHANGED
@@ -5,31 +5,97 @@
5
5
 
6
6
  [![wercker status](https://app.wercker.com/status/44c3ade6a2406d337df6d93097a52fdf/m "wercker status")](https://app.wercker.com/project/bykey/44c3ade6a2406d337df6d93097a52fdf)
7
7
 
8
- PerfMonger is an yet anothor performance measurement/monitoring tool
9
- speaking JSON.
8
+ PerfMonger is a system performance monitor which enables high-resolution and holistic performance measurement with the programmer friendly interface.
9
+
10
+ * High-resolution: sub-second level monitoring is possible!
11
+ * Holistic performance measurement: monitoring CPU, Disk I/O, Network all at once.
12
+ * Programmer friendly: PerfMonger speaks monitoring results in JSON format, which makes later performance analysis much easier (ex. [jq](https://github.com/stedolan/jq)).
10
13
 
11
14
  **CAUTION: PerfMonger is still in early stage, so there may be a drastic change in the future. Do not use it for critical jobs**
12
15
 
13
16
  ## Target platform
14
17
 
15
18
  * GNU/Linux
19
+ * Mac OS X (experimental support)
16
20
 
17
- ## Prerequisites
18
-
19
- * Ruby 1.9.3 or later
20
- * gnuplot 4.6.0 or later (optional)
21
-
22
- Note: You need Cutter unit testing framework for building/running tests.
23
-
24
- ## How to install
21
+ ## How to installation
25
22
 
26
23
  gem install perfmonger
27
24
 
25
+ You need gnuplot 4.6.0 or later build with cairo terminals for plotting measurement data with `perfmonger plot` command.
26
+
28
27
  ### Build from source
29
28
 
29
+ You need Ruby 1.9.3 or later, and Go 1.8 or later to build perfmonger.
30
+
31
+ bundle
30
32
  rake build
31
33
 
32
- ## How to use: case study
34
+ ## Getting started
35
+
36
+ Basic usage of PerfMonger is:
37
+
38
+ * Run `perfmonger record` to record performance information logs
39
+ * Run `perfmonger play` to show performance information logs in JSON format
40
+
41
+ `perfmonger play` repatedly prints records of system performance including CPU
42
+ usages, disk usages, and network usages. One line includes only one record, so
43
+ you can easily process output records with `jq`, or any scripting languages like
44
+ Ruby, Python, and etc.
45
+
46
+ Pretty-printed structure of a record is as follows:
47
+
48
+ ```
49
+ {"time": 1500043743.504, # timestamp in unix epoch time
50
+ "cpu": { # CPU usages
51
+ "num_core": 2, # the number of cores
52
+ "all": { # aggregated CPU usage (max = num_core * 100%)
53
+ "usr": 50.0,
54
+ "sys": 50.0,
55
+ "idle": 100.0,
56
+ ...
57
+ },
58
+ "cores": [ # Usage of each CPU core
59
+ {
60
+ "usr": 25.0,
61
+ "sys": 25.0,
62
+ "idle": 50.0,
63
+ ...
64
+ },
65
+ {
66
+ "usr": 25.0,
67
+ "sys": 25.0,
68
+ "idle": 50.0,
69
+ ...
70
+ }
71
+ ]
72
+ },
73
+ "disk": { # Disk usages
74
+ "devices": ["sda"], # List of disk devices
75
+ "sda": { # Usage of device 'sda'
76
+ "riops": 10.0, # The number of read I/O per second
77
+ "wiops": 20.0, # The number of write I/O per second
78
+ "rkbyteps": 80.0, # Read transfer rate in KB/s
79
+ "wkbyteps": 160.0, # Write transfer rate in KB/s
80
+ ...
81
+ }
82
+ "total": { # Aggregated usage of all devices
83
+ "riops": 10.0,
84
+ "wiops": 20.0,
85
+ "rkbyteps": 80.0,
86
+ "wkbyteps": 160.0,
87
+ ...
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+
94
+ ## Typical use cases
95
+
96
+ ### `perfmonger live`: live performance monitoring
97
+
98
+ $ perfmonger live
33
99
 
34
100
  ### Monitor IO performance of /dev/sda for each 0.1 second
35
101
 
@@ -42,19 +108,3 @@ Note: You need Cutter unit testing framework for building/running tests.
42
108
  ### Monitor CPU usage and IO performance of /dev/sda, sdb for each 0.1 second
43
109
 
44
110
  $ perfmonger record -i 0.1 -d sda -d sdb
45
-
46
- ### Plot CPU and IOPS
47
-
48
- $ perfmonger record -i 0.1 -C -d sda > /tmp/perfmonger.log & sleep 10; pkill perfmonger
49
- $ perfmonger plot -o /path/to/output_dir/ -Tpng /tmp/perfmonger.log
50
- $ display /path/to/output_dir/read-iops.png
51
- $ display /path/to/output_dir/cpu.png
52
-
53
- ![Sample image of IOPS graph](https://raw.github.com/hayamiz/perfmonger/master/misc/sample-read-iops.png)
54
- ![Sample image of CPU usage graph](https://raw.github.com/hayamiz/perfmonger/master/misc/sample-cpu.png)
55
-
56
- ## Special Thanks
57
-
58
- Large portion of PerfMonger comes from
59
- [SYSSTAT](http://sebastien.godard.pagesperso-orange.fr/) codebase. Thanks for
60
- their great work.
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ task :default => [:spec, :test_core]
7
7
  desc "Run all specs in spec directory"
8
8
  RSpec::Core::RakeTask.new(:spec)
9
9
 
10
- task :spec => [:self_build_core]
10
+ task :spec => [:cross_build_core]
11
11
 
12
12
  desc "Cross build core recorder/player"
13
13
  task :cross_build_core do
@@ -18,20 +18,29 @@ task :cross_build_core do
18
18
  end
19
19
  end
20
20
 
21
- desc "Self build core recorder/player"
22
- task :self_build_core do
23
- Dir.chdir("./core") do
24
- sh "./build.sh -"
25
- end
26
- end
27
-
28
21
  task :build => :cross_build_core
29
22
 
23
+ desc "Install Golang libraries"
24
+ task :go_get do
25
+ sh "go get -u github.com/hayamiz/go-projson"
26
+ sh "go get -u github.com/hayamiz/perfmonger/core/subsystem"
27
+ sh "go get -u golang.org/x/crypto/ssh/terminal"
28
+ sh "go get -u github.com/mattn/go-isatty"
29
+ end
30
+
30
31
  desc "Run tests of core recorder/player"
31
- task :test_core do
32
+ task :test_core => [:cross_build_core] do
32
33
  Dir.chdir("./core/subsystem") do
33
34
  sh "go test -v -cover"
35
+
36
+ # running static analysis
37
+ sh "go vet *.go"
34
38
  end
39
+
40
+ Dir.chdir("./core") do
41
+ sh "go vet *.go"
42
+ end
43
+
35
44
  end
36
45
 
37
46
  desc "Removed generated files"
data/core/Makefile CHANGED
@@ -9,22 +9,6 @@ GO_SRC := utils.go
9
9
  all: build
10
10
 
11
11
 
12
- ../lib/exec/perfmonger-recorder_linux_386: perfmonger-recorder.go $(GO_DEPS)
13
- go build -o $@ perfmonger-recorder.go $(GO_SRC)
14
-
15
-
16
- ../lib/exec/perfmonger-player_linux_386: perfmonger-player.go $(GO_DEPS)
17
- go build -o $@ perfmonger-player.go $(GO_SRC)
18
-
19
-
20
- ../lib/exec/perfmonger-summarizer_linux_386: perfmonger-summarizer.go $(GO_DEPS)
21
- go build -o $@ perfmonger-summarizer.go $(GO_SRC)
22
-
23
-
24
- ../lib/exec/perfmonger-plot-formatter_linux_386: perfmonger-plot-formatter.go $(GO_DEPS)
25
- go build -o $@ perfmonger-plot-formatter.go $(GO_SRC)
26
-
27
-
28
12
  ../lib/exec/perfmonger-recorder_linux_amd64: perfmonger-recorder.go $(GO_DEPS)
29
13
  go build -o $@ perfmonger-recorder.go $(GO_SRC)
30
14
 
@@ -57,8 +41,8 @@ all: build
57
41
  go build -o $@ perfmonger-plot-formatter.go $(GO_SRC)
58
42
 
59
43
 
60
- build: ../lib/exec/perfmonger-recorder_linux_386 ../lib/exec/perfmonger-player_linux_386 ../lib/exec/perfmonger-summarizer_linux_386 ../lib/exec/perfmonger-plot-formatter_linux_386 ../lib/exec/perfmonger-recorder_linux_amd64 ../lib/exec/perfmonger-player_linux_amd64 ../lib/exec/perfmonger-summarizer_linux_amd64 ../lib/exec/perfmonger-plot-formatter_linux_amd64 ../lib/exec/perfmonger-recorder_darwin_amd64 ../lib/exec/perfmonger-player_darwin_amd64 ../lib/exec/perfmonger-summarizer_darwin_amd64 ../lib/exec/perfmonger-plot-formatter_darwin_amd64
44
+ build: ../lib/exec/perfmonger-recorder_linux_amd64 ../lib/exec/perfmonger-player_linux_amd64 ../lib/exec/perfmonger-summarizer_linux_amd64 ../lib/exec/perfmonger-plot-formatter_linux_amd64 ../lib/exec/perfmonger-recorder_darwin_amd64 ../lib/exec/perfmonger-player_darwin_amd64 ../lib/exec/perfmonger-summarizer_darwin_amd64 ../lib/exec/perfmonger-plot-formatter_darwin_amd64
61
45
 
62
46
  clean:
63
- rm -f ../lib/exec/perfmonger-recorder_linux_386 ../lib/exec/perfmonger-player_linux_386 ../lib/exec/perfmonger-summarizer_linux_386 ../lib/exec/perfmonger-plot-formatter_linux_386 ../lib/exec/perfmonger-recorder_linux_amd64 ../lib/exec/perfmonger-player_linux_amd64 ../lib/exec/perfmonger-summarizer_linux_amd64 ../lib/exec/perfmonger-plot-formatter_linux_amd64 ../lib/exec/perfmonger-recorder_darwin_amd64 ../lib/exec/perfmonger-player_darwin_amd64 ../lib/exec/perfmonger-summarizer_darwin_amd64 ../lib/exec/perfmonger-plot-formatter_darwin_amd64
47
+ rm -f ../lib/exec/perfmonger-recorder_linux_amd64 ../lib/exec/perfmonger-player_linux_amd64 ../lib/exec/perfmonger-summarizer_linux_amd64 ../lib/exec/perfmonger-plot-formatter_linux_amd64 ../lib/exec/perfmonger-recorder_darwin_amd64 ../lib/exec/perfmonger-player_darwin_amd64 ../lib/exec/perfmonger-summarizer_darwin_amd64 ../lib/exec/perfmonger-plot-formatter_darwin_amd64
64
48
 
data/core/build.sh CHANGED
@@ -28,7 +28,7 @@ if [[ $1 = "-" ]]; then
28
28
  TARGET=("${os} ${arch}")
29
29
  else
30
30
  # cross build
31
- TARGET=("linux 386" "linux amd64" "darwin amd64")
31
+ TARGET=("linux amd64" "darwin amd64")
32
32
  fi
33
33
 
34
34
  set -e
@@ -4,39 +4,38 @@ package main
4
4
 
5
5
  import (
6
6
  "bufio"
7
- "bytes"
8
7
  "encoding/gob"
8
+ "flag"
9
9
  "fmt"
10
10
  "io"
11
11
  "os"
12
12
 
13
+ projson "github.com/hayamiz/go-projson"
13
14
  ss "github.com/hayamiz/perfmonger/core/subsystem"
14
15
  )
15
16
 
16
- func showCpuStat(buffer *bytes.Buffer, prev_rec *ss.StatRecord, cur_rec *ss.StatRecord) error {
17
+ type PlayerOption struct {
18
+ logfile string
19
+ color bool
20
+ pretty bool
21
+ }
22
+
23
+ var option PlayerOption
24
+ var init_rec ss.StatRecord
25
+
26
+ func showCpuStat(printer *projson.JsonPrinter, prev_rec *ss.StatRecord, cur_rec *ss.StatRecord) error {
17
27
  cusage, err := ss.GetCpuUsage(prev_rec.Cpu, cur_rec.Cpu)
18
28
  if err != nil {
19
29
  return err
20
30
  }
21
- buffer.WriteString(`,"cpu":`)
22
- cusage.WriteJsonTo(buffer)
31
+
32
+ printer.PutKey("cpu")
33
+ cusage.WriteJsonTo(printer)
23
34
 
24
35
  return nil
25
36
  }
26
37
 
27
- func showInterruptStat(buffer *bytes.Buffer, prev_rec *ss.StatRecord, cur_rec *ss.StatRecord) error {
28
- // intr_usage, err := ss.GetInterruptUsage(
29
- // prev_rec.Time, prev_rec.Interrupt,
30
- // cur_rec.Time, cur_rec.Interrupt)
31
- // if err != nil {
32
- // return err
33
- // }
34
- //
35
- // buffer.WriteString(`,"intr":`)
36
- // intr_usage.WriteJsonTo(buffer)
37
- //
38
- // return nil
39
-
38
+ func showInterruptStat(printer *projson.JsonPrinter, prev_rec *ss.StatRecord, cur_rec *ss.StatRecord) error {
40
39
  intr_usage, err := ss.GetInterruptUsage(
41
40
  prev_rec.Time, prev_rec.Interrupt,
42
41
  cur_rec.Time, cur_rec.Interrupt)
@@ -44,13 +43,13 @@ func showInterruptStat(buffer *bytes.Buffer, prev_rec *ss.StatRecord, cur_rec *s
44
43
  return err
45
44
  }
46
45
 
47
- buffer.WriteString(`,"intr":`)
48
- intr_usage.WriteJsonTo(buffer)
46
+ printer.PutKey("intr")
47
+ intr_usage.WriteJsonTo(printer)
49
48
 
50
49
  return nil
51
50
  }
52
51
 
53
- func showDiskStat(buffer *bytes.Buffer, prev_rec *ss.StatRecord, cur_rec *ss.StatRecord) error {
52
+ func showDiskStat(printer *projson.JsonPrinter, prev_rec *ss.StatRecord, cur_rec *ss.StatRecord) error {
54
53
  dusage, err := ss.GetDiskUsage(
55
54
  prev_rec.Time, prev_rec.Disk,
56
55
  cur_rec.Time, cur_rec.Disk)
@@ -58,13 +57,14 @@ func showDiskStat(buffer *bytes.Buffer, prev_rec *ss.StatRecord, cur_rec *ss.Sta
58
57
  return err
59
58
  }
60
59
 
61
- buffer.WriteString(`,"disk":`)
62
- dusage.WriteJsonTo(buffer)
60
+ printer.PutKey("disk")
61
+
62
+ dusage.WriteJsonTo(printer)
63
63
 
64
64
  return nil
65
65
  }
66
66
 
67
- func showNetStat(buffer *bytes.Buffer, prev_rec *ss.StatRecord, cur_rec *ss.StatRecord) error {
67
+ func showNetStat(printer *projson.JsonPrinter, prev_rec *ss.StatRecord, cur_rec *ss.StatRecord) error {
68
68
  dusage, err := ss.GetNetUsage(
69
69
  prev_rec.Time, prev_rec.Net,
70
70
  cur_rec.Time, cur_rec.Net,
@@ -73,52 +73,77 @@ func showNetStat(buffer *bytes.Buffer, prev_rec *ss.StatRecord, cur_rec *ss.Stat
73
73
  return err
74
74
  }
75
75
 
76
- buffer.WriteString(`,"net":`)
77
- dusage.WriteJsonTo(buffer)
76
+ printer.PutKey("net")
77
+
78
+ dusage.WriteJsonTo(printer)
78
79
 
79
80
  return nil
80
81
  }
81
82
 
82
- func showStat(buffer *bytes.Buffer, prev_rec *ss.StatRecord, cur_rec *ss.StatRecord) error {
83
- buffer.WriteString(fmt.Sprintf(`{"time":%.3f`, float64(cur_rec.Time.UnixNano())/1e9))
83
+ func showStat(printer *projson.JsonPrinter, prev_rec *ss.StatRecord, cur_rec *ss.StatRecord) error {
84
+ printer.Reset()
85
+ if option.pretty {
86
+ printer.SetStyle(projson.SmartStyle)
87
+ }
88
+ if option.color {
89
+ printer.SetColor(true)
90
+ }
91
+ printer.BeginObject()
92
+ printer.PutKey("time")
93
+ printer.PutFloatFmt(float64(cur_rec.Time.UnixNano())/1e9, "%.3f")
94
+ printer.PutKey("elapsed_time")
95
+ printer.PutFloatFmt((float64(cur_rec.Time.UnixNano())-float64(init_rec.Time.UnixNano()))/1e9,
96
+ "%.3f")
97
+
84
98
  if cur_rec.Cpu != nil {
85
- err := showCpuStat(buffer, prev_rec, cur_rec)
99
+ err := showCpuStat(printer, prev_rec, cur_rec)
86
100
  if err != nil {
87
101
  return err
88
102
  }
89
103
  }
90
104
  if cur_rec.Interrupt != nil {
91
- err := showInterruptStat(buffer, prev_rec, cur_rec)
105
+ err := showInterruptStat(printer, prev_rec, cur_rec)
92
106
  if err != nil {
93
107
  return err
94
108
  }
95
109
  }
96
110
  if cur_rec.Disk != nil {
97
- err := showDiskStat(buffer, prev_rec, cur_rec)
111
+ err := showDiskStat(printer, prev_rec, cur_rec)
98
112
  if err != nil {
99
113
  return err
100
114
  }
101
115
  }
102
116
  if cur_rec.Net != nil {
103
- err := showNetStat(buffer, prev_rec, cur_rec)
117
+ err := showNetStat(printer, prev_rec, cur_rec)
104
118
  if err != nil {
105
119
  return err
106
120
  }
107
121
  }
108
- buffer.WriteString("}\n")
122
+
123
+ printer.FinishObject()
109
124
 
110
125
  return nil
111
126
  }
112
127
 
128
+ func parseArgs() {
129
+ flag.BoolVar(&option.color, "color", false, "Use colored JSON output")
130
+ flag.BoolVar(&option.pretty, "pretty", false, "Use human readable JSON output")
131
+
132
+ flag.Parse()
133
+
134
+ option.logfile = flag.Arg(0)
135
+ }
136
+
113
137
  func main() {
114
- args := os.Args
115
138
  var in *os.File
116
139
  var out *bufio.Writer
117
140
 
118
- if len(args) < 2 {
141
+ parseArgs()
142
+
143
+ if option.logfile == "" {
119
144
  in = os.Stdin
120
145
  } else {
121
- f, err := os.Open(args[1])
146
+ f, err := os.Open(option.logfile)
122
147
  if err != nil {
123
148
  panic(err)
124
149
  }
@@ -159,9 +184,10 @@ func main() {
159
184
  } else if err != nil {
160
185
  panic(err)
161
186
  }
187
+ init_rec = records[curr]
162
188
  curr ^= 1
163
189
 
164
- buffer := bytes.NewBuffer([]byte{})
190
+ printer := projson.NewPrinter()
165
191
  for {
166
192
  prev_rec := &records[curr^1]
167
193
  cur_rec := &records[curr]
@@ -173,21 +199,26 @@ func main() {
173
199
  panic(err)
174
200
  }
175
201
 
176
- err = showStat(buffer, prev_rec, cur_rec)
202
+ err = showStat(printer, prev_rec, cur_rec)
177
203
  if err != nil {
178
- buffer.Reset()
204
+ printer.Reset()
179
205
  fmt.Fprintln(os.Stderr, "skip by err")
180
206
  continue
181
207
  }
182
208
 
183
- _, err = out.WriteString(buffer.String())
209
+ if str, err := printer.String(); err != nil {
210
+ fmt.Println("error", err)
211
+ fmt.Println(str)
212
+ } else {
213
+ _, err = out.WriteString(str + "\n")
214
+ }
184
215
  err = out.Flush()
185
216
  if err != nil {
186
217
  // stdout is closed
187
218
  break
188
219
  }
189
220
 
190
- buffer.Reset()
221
+ printer.Reset()
191
222
 
192
223
  curr ^= 1
193
224
  }