3
3
extern crate hdrsample;
4
4
extern crate clap;
5
5
6
- use std:: io:: BufRead ;
6
+ use std:: io;
7
+ use std:: io:: { Write , BufRead } ;
8
+ use std:: fmt:: Display ;
7
9
8
10
use clap:: { App , Arg , SubCommand } ;
9
11
10
- use hdrsample:: Histogram ;
11
- use hdrsample:: serialization:: { V2Serializer , V2DeflateSerializer } ;
12
+ use hdrsample:: { Histogram , RecordError } ;
13
+ use hdrsample:: serialization:: { V2Serializer , V2SerializeError , V2DeflateSerializer , V2DeflateSerializeError , Deserializer , DeserializeError } ;
12
14
13
15
fn main ( ) {
14
16
let default_max = format ! ( "{}" , u64 :: max_value( ) ) ;
15
17
let matches = App :: new ( "hdrsample cli" )
16
18
. subcommand ( SubCommand :: with_name ( "serialize" )
19
+ . about ( "Transform number-per-line input from stdin into a serialized histogram on stdout" )
17
20
. arg ( Arg :: with_name ( "min" )
18
21
. long ( "min" )
19
22
. help ( "Minimum discernible value" )
@@ -37,8 +40,26 @@ fn main() {
37
40
. short ( "r" )
38
41
. long ( "resize" )
39
42
. help ( "Enable auto resize" ) ) )
43
+ . subcommand ( SubCommand :: with_name ( "iter-quantiles" )
44
+ . about ( "Display quantiles to stdout from serialized histogram stdin" )
45
+ . arg ( Arg :: with_name ( "ticks" )
46
+ . short ( "t" )
47
+ . long ( "ticks-per-half" )
48
+ . takes_value ( true )
49
+ . required ( true )
50
+ . help ( "Ticks per half distance" ) )
51
+ . arg ( Arg :: with_name ( "quantile-precision" )
52
+ . long ( "quantile-precision" )
53
+ . takes_value ( true )
54
+ . default_value ( "20" ) ) )
40
55
. get_matches ( ) ;
41
56
57
+ let stdin = std:: io:: stdin ( ) ;
58
+ let stdin = stdin. lock ( ) ;
59
+
60
+ let stdout = std:: io:: stdout ( ) ;
61
+ let stdout = stdout. lock ( ) ;
62
+
42
63
match matches. subcommand_name ( ) {
43
64
Some ( "serialize" ) => {
44
65
let sub_matches = matches. subcommand_matches ( "serialize" ) . unwrap ( ) ;
@@ -52,28 +73,135 @@ fn main() {
52
73
h. auto ( true ) ;
53
74
}
54
75
55
- serialize ( h, sub_matches. is_present ( "compression" ) ) ;
56
- } ,
76
+ serialize ( stdin, stdout, h, sub_matches. is_present ( "compression" ) )
77
+ }
78
+ Some ( "iter-quantiles" ) => {
79
+ let sub_matches = matches. subcommand_matches ( "iter-quantiles" ) . unwrap ( ) ;
80
+ let ticks_per_half = sub_matches. value_of ( "ticks" ) . unwrap ( ) . parse ( ) . unwrap ( ) ;
81
+ let quantile_precision = sub_matches. value_of ( "quantile-precision" ) . unwrap ( ) . parse ( ) . unwrap ( ) ;
82
+ quantiles ( stdin, stdout, quantile_precision, ticks_per_half)
83
+ }
57
84
_ => unreachable ! ( )
58
- }
85
+ } . expect ( "Subcommand failed" )
59
86
}
60
87
61
- fn serialize ( mut h : Histogram < u64 > , compression : bool ) {
62
- let stdin = std:: io:: stdin ( ) ;
63
- let stdin_handle = stdin. lock ( ) ;
64
-
65
- for num in stdin_handle. lines ( )
88
+ /// Read numbers, one from each line, from stdin and output the resulting serialized histogram.
89
+ fn serialize < R : BufRead , W : Write > ( reader : R , mut writer : W , mut h : Histogram < u64 > , compression : bool ) -> Result < ( ) , CliError > {
90
+ for num in reader. lines ( )
66
91
. map ( |l| l. expect ( "Should be able to read stdin" ) )
67
92
. map ( |s| s. parse ( ) . expect ( "Each line must be a u64" ) ) {
68
- h. record ( num) . unwrap ( ) ;
93
+ h. record ( num) ? ;
69
94
}
70
95
71
- let stdout = std:: io:: stdout ( ) ;
72
- let mut stdout_handle = stdout. lock ( ) ;
73
-
74
96
if compression {
75
- V2DeflateSerializer :: new ( ) . serialize ( & h, & mut stdout_handle ) . unwrap ( ) ;
97
+ V2DeflateSerializer :: new ( ) . serialize ( & h, & mut writer ) ? ;
76
98
} else {
77
- V2Serializer :: new ( ) . serialize ( & h, & mut stdout_handle) . unwrap ( ) ;
99
+ V2Serializer :: new ( ) . serialize ( & h, & mut writer) ?;
100
+ }
101
+
102
+ Ok ( ( ) )
103
+ }
104
+
105
+ /// Output histogram data in a format similar to the Java impl's
106
+ /// `AbstractHistogram#outputPercentileDistribution`.
107
+ fn quantiles < R : BufRead , W : Write > ( mut reader : R , mut writer : W , quantile_precision : usize , ticks_per_half : u32 ) -> Result < ( ) , CliError > {
108
+ let hist: Histogram < u64 > = Deserializer :: new ( ) . deserialize ( & mut reader) ?;
109
+
110
+ writer. write_all (
111
+ format ! (
112
+ "{:>12} {:>quantile_precision$} {:>quantile_precision$} {:>10} {:>14}\n \n " ,
113
+ "Value" ,
114
+ "QuantileValue" ,
115
+ "QuantileIteration" ,
116
+ "TotalCount" ,
117
+ "1/(1-Quantile)" ,
118
+ quantile_precision = quantile_precision + 2 // + 2 from leading "0." for numbers
119
+ ) . as_ref ( ) ,
120
+ ) ?;
121
+ let mut sum = 0 ;
122
+ for v in hist. iter_quantiles ( ticks_per_half) {
123
+ sum += v. count_since_last_iteration ( ) ;
124
+ if v. quantile ( ) < 1.0 {
125
+ writer. write_all (
126
+ format ! (
127
+ "{:12} {:1.*} {:1.*} {:10} {:14.2}\n " ,
128
+ v. value( ) ,
129
+ quantile_precision,
130
+ v. quantile( ) ,
131
+ quantile_precision,
132
+ v. quantile_iterated_to( ) ,
133
+ sum,
134
+ 1_f64 / ( 1_f64 - v. quantile( ) )
135
+ ) . as_ref ( ) ,
136
+ ) ?;
137
+ } else {
138
+ writer. write_all (
139
+ format ! (
140
+ "{:12} {:1.*} {:1.*} {:10} {:>14}\n " ,
141
+ v. value( ) ,
142
+ quantile_precision,
143
+ v. quantile( ) ,
144
+ quantile_precision,
145
+ v. quantile_iterated_to( ) ,
146
+ sum,
147
+ "∞"
148
+ ) . as_ref ( ) ,
149
+ ) ?;
150
+ }
151
+ }
152
+
153
+ fn write_extra_data < T1 : Display , T2 : Display , W : Write > (
154
+ writer : & mut W , label1 : & str , data1 : T1 , label2 : & str , data2 : T2 ) -> Result < ( ) , io:: Error > {
155
+ writer. write_all ( format ! ( "#[{:10} = {:12.2}, {:14} = {:12.2}]\n " ,
156
+ label1, data1, label2, data2) . as_ref ( ) )
157
+ }
158
+
159
+ write_extra_data ( & mut writer, "Mean" , hist. mean ( ) , "StdDeviation" , hist. stdev ( ) ) ?;
160
+ write_extra_data ( & mut writer, "Max" , hist. max ( ) , "Total count" , hist. count ( ) ) ?;
161
+ write_extra_data ( & mut writer, "Buckets" , hist. buckets ( ) , "SubBuckets" , hist. len ( ) ) ?;
162
+
163
+ Ok ( ( ) )
164
+ }
165
+
166
+
167
+ // A handy way to enable ? use in subcommands by mapping common errors.
168
+ // Normally I frown on excessive use of From as it's too "magic", but in the limited confines of
169
+ // subcommands, the convenience seems worth it.
170
+ #[ derive( Debug ) ]
171
+ enum CliError {
172
+ IoError ( io:: Error ) ,
173
+ HistogramSerializeError ( V2SerializeError ) ,
174
+ HistogramSerializeCompressedError ( V2DeflateSerializeError ) ,
175
+ HistogramDeserializeError ( DeserializeError ) ,
176
+ HistogramRecordError ( RecordError )
177
+ }
178
+
179
+ impl From < io:: Error > for CliError {
180
+ fn from ( e : io:: Error ) -> Self {
181
+ CliError :: IoError ( e)
182
+ }
183
+ }
184
+
185
+ impl From < V2SerializeError > for CliError {
186
+ fn from ( e : V2SerializeError ) -> Self {
187
+ CliError :: HistogramSerializeError ( e)
188
+ }
189
+ }
190
+
191
+ impl From < V2DeflateSerializeError > for CliError {
192
+ fn from ( e : V2DeflateSerializeError ) -> Self {
193
+ CliError :: HistogramSerializeCompressedError ( e)
194
+ }
195
+ }
196
+
197
+ impl From < RecordError > for CliError {
198
+ fn from ( e : RecordError ) -> Self {
199
+ CliError :: HistogramRecordError ( e)
200
+ }
201
+ }
202
+
203
+ impl From < DeserializeError > for CliError {
204
+ fn from ( e : DeserializeError ) -> Self {
205
+ CliError :: HistogramDeserializeError ( e)
78
206
}
79
207
}
0 commit comments