/Users/deen/code/yugabyte-db/src/yb/gutil/strings/human_readable.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2007 Google Inc. All Rights Reserved. |
2 | | // |
3 | | // The following only applies to changes made to this file as part of YugaByte development. |
4 | | // |
5 | | // Portions Copyright (c) YugaByte, Inc. |
6 | | // |
7 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
8 | | // in compliance with the License. You may obtain a copy of the License at |
9 | | // |
10 | | // http://www.apache.org/licenses/LICENSE-2.0 |
11 | | // |
12 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
13 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
14 | | // or implied. See the License for the specific language governing permissions and limitations |
15 | | // under the License. |
16 | | // |
17 | | |
18 | | #include "yb/gutil/strings/human_readable.h" |
19 | | |
20 | | #include <stddef.h> |
21 | | #include <string.h> |
22 | | |
23 | | #include <glog/logging.h> |
24 | | #include "yb/gutil/stringprintf.h" |
25 | | #include "yb/gutil/strings/strip.h" |
26 | | |
27 | | namespace { |
28 | | |
29 | | template <typename T> |
30 | 1.92k | const char* GetNegStr(T* value) { |
31 | 1.92k | if (*value < 0) { |
32 | 0 | *value = -(*value); |
33 | 0 | return "-"; |
34 | 1.92k | } else { |
35 | 1.92k | return ""; |
36 | 1.92k | } |
37 | 1.92k | } Unexecuted instantiation: human_readable.cc:char const* (anonymous namespace)::GetNegStr<double>(double*) human_readable.cc:char const* (anonymous namespace)::GetNegStr<long long>(long long*) Line | Count | Source | 30 | 1.92k | const char* GetNegStr(T* value) { | 31 | 1.92k | if (*value < 0) { | 32 | 0 | *value = -(*value); | 33 | 0 | return "-"; | 34 | 1.92k | } else { | 35 | 1.92k | return ""; | 36 | 1.92k | } | 37 | 1.92k | } |
|
38 | | |
39 | | } // namespace |
40 | | |
41 | 0 | bool HumanReadableNumBytes::LessThan(const string &a, const string &b) { |
42 | 0 | int64 a_bytes, b_bytes; |
43 | 0 | if (!HumanReadableNumBytes::ToInt64(a, &a_bytes)) |
44 | 0 | a_bytes = 0; |
45 | 0 | if (!HumanReadableNumBytes::ToInt64(b, &b_bytes)) |
46 | 0 | b_bytes = 0; |
47 | 0 | return (a_bytes < b_bytes); |
48 | 0 | } |
49 | | |
50 | 2 | bool HumanReadableNumBytes::ToInt64(const string &str, int64 *num_bytes) { |
51 | 2 | const char *cstr = str.c_str(); |
52 | 2 | bool neg = (*cstr == '-'); |
53 | 2 | if (neg) { |
54 | 0 | cstr++; |
55 | 0 | } |
56 | 2 | char *end; |
57 | 2 | double d = strtod(cstr, &end); |
58 | | // If this didn't consume the entire string, fail. |
59 | 2 | if (end + 1 < str.c_str() + str.size()) { |
60 | 0 | return false; |
61 | 0 | } |
62 | 2 | int64 scale = 1; |
63 | 2 | switch (*end) { |
64 | | // NB: an int64 can only go up to <8 EB. |
65 | 0 | case 'E': scale <<= 10; FALLTHROUGH_INTENDED; // Fall through... |
66 | 0 | case 'P': scale <<= 10; FALLTHROUGH_INTENDED; |
67 | 0 | case 'T': scale <<= 10; FALLTHROUGH_INTENDED; |
68 | 0 | case 'G': scale <<= 10; FALLTHROUGH_INTENDED; |
69 | 2 | case 'M': scale <<= 10; FALLTHROUGH_INTENDED; |
70 | 2 | case 'K': FALLTHROUGH_INTENDED; |
71 | 2 | case 'k': scale <<= 10; FALLTHROUGH_INTENDED; |
72 | 2 | case 'B': FALLTHROUGH_INTENDED; |
73 | 2 | case '\0': break; // To here. |
74 | 0 | default: |
75 | 0 | return false; |
76 | 2 | } |
77 | 2 | d *= scale; |
78 | | // We have to cast kint64max to double to avoid a warning on implicit cast that changes the value. |
79 | 2 | if (d > static_cast<double>(kint64max) || d < 0) |
80 | 0 | return false; |
81 | 2 | *num_bytes = static_cast<int64>(d + 0.5); |
82 | 2 | if (neg) { |
83 | 0 | *num_bytes = -*num_bytes; |
84 | 0 | } |
85 | 2 | return true; |
86 | 2 | } |
87 | | |
88 | 0 | bool HumanReadableNumBytes::ToDouble(const string &str, double *num_bytes) { |
89 | 0 | char *end; |
90 | 0 | double d = strtod(str.c_str(), &end); |
91 | | // If this didn't consume the entire string, fail. |
92 | 0 | if (end + 1 < str.c_str() + str.size()) |
93 | 0 | return false; |
94 | 0 | const char scale = *end; |
95 | 0 | switch (scale) { |
96 | 0 | case 'Y': d *= 1024.0; FALLTHROUGH_INTENDED; // That's a yotta bytes! |
97 | 0 | case 'Z': d *= 1024.0; FALLTHROUGH_INTENDED; |
98 | 0 | case 'E': d *= 1024.0; FALLTHROUGH_INTENDED; |
99 | 0 | case 'P': d *= 1024.0; FALLTHROUGH_INTENDED; |
100 | 0 | case 'T': d *= 1024.0; FALLTHROUGH_INTENDED; |
101 | 0 | case 'G': d *= 1024.0; FALLTHROUGH_INTENDED; |
102 | 0 | case 'M': d *= 1024.0; FALLTHROUGH_INTENDED; |
103 | 0 | case 'K': FALLTHROUGH_INTENDED; |
104 | 0 | case 'k': d *= 1024.0; FALLTHROUGH_INTENDED; |
105 | 0 | case 'B': FALLTHROUGH_INTENDED; |
106 | 0 | case '\0': break; // to here. |
107 | 0 | default: |
108 | 0 | return false; |
109 | 0 | } |
110 | 0 | *num_bytes = d; |
111 | 0 | return true; |
112 | 0 | } |
113 | | |
114 | 0 | string HumanReadableNumBytes::DoubleToString(double num_bytes) { |
115 | 0 | const char *neg_str = GetNegStr(&num_bytes); |
116 | 0 | static const char units[] = "BKMGTPEZY"; |
117 | 0 | double scaled = num_bytes; |
118 | 0 | size_t i = 0; |
119 | 0 | for (; i < arraysize(units) && scaled >= 1024.0; ++i) { |
120 | 0 | scaled /= 1024.0; |
121 | 0 | } |
122 | 0 | if (i == arraysize(units)) { |
123 | 0 | return StringPrintf("%s%g", neg_str, num_bytes); |
124 | 0 | } else { |
125 | 0 | return StringPrintf("%s%.2f%c", neg_str, scaled, units[i]); |
126 | 0 | } |
127 | 0 | } |
128 | | |
129 | 1.92k | string HumanReadableNumBytes::ToString(int64 num_bytes) { |
130 | 1.92k | if (num_bytes == kint64min) { |
131 | | // Special case for number with not representable nagation. |
132 | 0 | return "-8E"; |
133 | 0 | } |
134 | | |
135 | 1.92k | const char *neg_str = GetNegStr(&num_bytes); |
136 | | |
137 | | // Special case for bytes. |
138 | 1.92k | if (num_bytes < GG_LONGLONG(1024)) { |
139 | | // No fractions for bytes. |
140 | 1.09k | return StringPrintf("%s%" PRId64 "B", neg_str, num_bytes); |
141 | 1.09k | } |
142 | | |
143 | 829 | static const char units[] = "KMGTPE"; // int64 only goes up to E. |
144 | 829 | const char* unit = units; |
145 | 1.48k | while (num_bytes >= GG_LONGLONG(1024) * GG_LONGLONG(1024)) { |
146 | 651 | num_bytes /= GG_LONGLONG(1024); |
147 | 651 | ++unit; |
148 | 651 | CHECK(unit < units + arraysize(units)); |
149 | 651 | } |
150 | | |
151 | 829 | return StringPrintf(((*unit == 'K') |
152 | 829 | ? "%s%.1f%c"180 |
153 | 829 | : "%s%.2f%c"649 ), neg_str, num_bytes / 1024.0, *unit); |
154 | 1.92k | } |
155 | | |
156 | 0 | string HumanReadableNumBytes::ToStringWithoutRounding(int64 num_bytes) { |
157 | 0 | if (num_bytes == kint64min) { |
158 | | // Special case for number with not representable nagation. |
159 | 0 | return "-8E"; |
160 | 0 | } |
161 | | |
162 | 0 | const char *neg_str = GetNegStr(&num_bytes); |
163 | 0 | static const char units[] = "BKMGTPE"; // int64 only goes up to E. |
164 | |
|
165 | 0 | int64 num_units = num_bytes; |
166 | 0 | size_t unit_type = 0; |
167 | 0 | for (; unit_type < arraysize(units); unit_type++) { |
168 | 0 | if (num_units % 1024 != 0) { |
169 | | // Not divisible by the next unit. |
170 | 0 | break; |
171 | 0 | } |
172 | | |
173 | 0 | int64 next_units = num_units >> 10; |
174 | 0 | if (next_units == 0) { |
175 | | // Less than the next unit. |
176 | 0 | break; |
177 | 0 | } |
178 | | |
179 | 0 | num_units = next_units; |
180 | 0 | } |
181 | 0 | return StringPrintf("%s%" PRId64 "%c", neg_str, num_units, units[unit_type]); |
182 | 0 | } |
183 | | |
184 | 0 | string HumanReadableInt::ToString(int64 value) { |
185 | 0 | string s; |
186 | 0 | if (value < 0) { |
187 | 0 | s += "-"; |
188 | 0 | value = -value; |
189 | 0 | } |
190 | 0 | if (value < GG_LONGLONG(1000)) { |
191 | 0 | StringAppendF(&s, "%" PRId64, value); |
192 | 0 | } else if (value >= GG_LONGLONG(1000000000000000)) { |
193 | | // Number bigger than 1E15; use that notation. |
194 | 0 | StringAppendF(&s, "%0.3G", static_cast<double>(value)); |
195 | 0 | } else { |
196 | 0 | static const char units[] = "kMBT"; |
197 | 0 | const char *unit = units; |
198 | 0 | while (value >= GG_LONGLONG(1000000)) { |
199 | 0 | value /= GG_LONGLONG(1000); |
200 | 0 | ++unit; |
201 | 0 | CHECK(unit < units + arraysize(units)); |
202 | 0 | } |
203 | 0 | StringAppendF(&s, "%.2f%c", value / 1000.0, *unit); |
204 | 0 | } |
205 | 0 | return s; |
206 | 0 | } |
207 | | |
208 | 0 | string HumanReadableNum::ToString(int64 value) { |
209 | 0 | return HumanReadableInt::ToString(value); |
210 | 0 | } |
211 | | |
212 | 0 | string HumanReadableNum::DoubleToString(double value) { |
213 | 0 | string s; |
214 | 0 | if (value < 0) { |
215 | 0 | s += "-"; |
216 | 0 | value = -value; |
217 | 0 | } |
218 | 0 | if (value < 1.0) { |
219 | 0 | StringAppendF(&s, "%.3f", value); |
220 | 0 | } else if (value < 10) { |
221 | 0 | StringAppendF(&s, "%.2f", value); |
222 | 0 | } else if (value < 1e2) { |
223 | 0 | StringAppendF(&s, "%.1f", value); |
224 | 0 | } else if (value < 1e3) { |
225 | 0 | StringAppendF(&s, "%.0f", value); |
226 | 0 | } else if (value >= 1e15) { |
227 | | // Number bigger than 1E15; use that notation. |
228 | 0 | StringAppendF(&s, "%0.3G", value); |
229 | 0 | } else { |
230 | 0 | static const char units[] = "kMBT"; |
231 | 0 | const char *unit = units; |
232 | 0 | while (value >= 1e6) { |
233 | 0 | value /= 1e3; |
234 | 0 | ++unit; |
235 | 0 | CHECK(unit < units + arraysize(units)); |
236 | 0 | } |
237 | 0 | StringAppendF(&s, "%.2f%c", value / 1000.0, *unit); |
238 | 0 | } |
239 | 0 | return s; |
240 | 0 | } |
241 | | |
242 | 0 | bool HumanReadableNum::ToDouble(const string &str, double *value) { |
243 | 0 | char *end; |
244 | 0 | double d = strtod(str.c_str(), &end); |
245 | | // Allow the string to contain at most one extra character: |
246 | 0 | if (end + 1 < str.c_str() + str.size()) |
247 | 0 | return false; |
248 | 0 | const char scale = *end; |
249 | 0 | if ((scale == 'k') || (scale == 'K')) { |
250 | 0 | d *= 1e3; |
251 | 0 | } else if (scale == 'M') { |
252 | 0 | d *= 1e6; |
253 | 0 | } else if (scale == 'B') { |
254 | 0 | d *= 1e9; |
255 | 0 | } else if (scale == 'T') { |
256 | 0 | d *= 1e12; |
257 | 0 | } else if (scale != '\0') { |
258 | 0 | return false; |
259 | 0 | } |
260 | 0 | *value = d; |
261 | 0 | return true; |
262 | 0 | } |
263 | | |
264 | 0 | bool HumanReadableInt::ToInt64(const string &str, int64 *value) { |
265 | 0 | char *end; |
266 | 0 | double d = strtod(str.c_str(), &end); |
267 | | // We have to cast kint64max to double to avoid a warning on implicit cast that changes the value. |
268 | 0 | if (d > static_cast<double>(kint64max) || d < kint64min) |
269 | 0 | return false; |
270 | 0 | if (*end == 'k') { |
271 | 0 | d *= 1000; |
272 | 0 | } else if (*end == 'M') { |
273 | 0 | d *= 1e6; |
274 | 0 | } else if (*end == 'B') { |
275 | 0 | d *= 1e9; |
276 | 0 | } else if (*end == 'T') { |
277 | 0 | d *= 1e12; |
278 | 0 | } else if (*end != '\0') { |
279 | 0 | return false; |
280 | 0 | } |
281 | 0 | *value = static_cast<int64>(d < 0 ? d - 0.5 : d + 0.5); |
282 | 0 | return true; |
283 | 0 | } |
284 | | |
285 | | // Abbreviations used here are acceptable English abbreviations |
286 | | // without the ending period (".") for brevity, except for uncommon |
287 | | // abbreviations, in which case the entire word is spelled out. ("mo" |
288 | | // and "mos" are not good abbreviations for "months" -- with or |
289 | | // without the period). If needed, one can add a |
290 | | // HumanReadableTime::ToStringShort() for shorter abbreviations or one |
291 | | // for always spelling out the unit, HumanReadableTime::ToStringLong(). |
292 | 351 | string HumanReadableElapsedTime::ToShortString(double seconds) { |
293 | 351 | string human_readable; |
294 | | |
295 | 351 | if (seconds < 0) { |
296 | 0 | human_readable = "-"; |
297 | 0 | seconds = -seconds; |
298 | 0 | } |
299 | | |
300 | | // Start with ns and keep going up to years. |
301 | 351 | if (seconds < 0.000001) { |
302 | 0 | StringAppendF(&human_readable, "%0.3g ns", seconds * 1000000000.0); |
303 | 0 | return human_readable; |
304 | 0 | } |
305 | 351 | if (seconds < 0.001) { |
306 | 0 | StringAppendF(&human_readable, "%0.3g us", seconds * 1000000.0); |
307 | 0 | return human_readable; |
308 | 0 | } |
309 | 351 | if (seconds < 1.0) { |
310 | 321 | StringAppendF(&human_readable, "%0.3g ms", seconds * 1000.0); |
311 | 321 | return human_readable; |
312 | 321 | } |
313 | 30 | if (seconds < 60.0) { |
314 | 30 | StringAppendF(&human_readable, "%0.3g s", seconds); |
315 | 30 | return human_readable; |
316 | 30 | } |
317 | 0 | seconds /= 60.0; |
318 | 0 | if (seconds < 60.0) { |
319 | 0 | StringAppendF(&human_readable, "%0.3g min", seconds); |
320 | 0 | return human_readable; |
321 | 0 | } |
322 | 0 | seconds /= 60.0; |
323 | 0 | if (seconds < 24.0) { |
324 | 0 | StringAppendF(&human_readable, "%0.3g h", seconds); |
325 | 0 | return human_readable; |
326 | 0 | } |
327 | 0 | seconds /= 24.0; |
328 | 0 | if (seconds < 30.0) { |
329 | 0 | StringAppendF(&human_readable, "%0.3g days", seconds); |
330 | 0 | return human_readable; |
331 | 0 | } |
332 | 0 | if (seconds < 365.2425) { |
333 | 0 | StringAppendF(&human_readable, "%0.3g months", seconds / 30.436875); |
334 | 0 | return human_readable; |
335 | 0 | } |
336 | 0 | seconds /= 365.2425; |
337 | 0 | StringAppendF(&human_readable, "%0.3g years", seconds); |
338 | 0 | return human_readable; |
339 | 0 | } |
340 | | |
341 | 0 | bool HumanReadableElapsedTime::ToDouble(const string& str, double* value) { |
342 | 0 | struct TimeUnits { |
343 | 0 | const char* unit; // unit name |
344 | 0 | double seconds; // number of seconds in that unit (minutes => 60) |
345 | 0 | }; |
346 | | |
347 | | // These must be sorted in decreasing length. In particulary, a |
348 | | // string must exist before and of its substrings or the substring |
349 | | // will match; |
350 | 0 | static const TimeUnits kUnits[] = { |
351 | | // Long forms |
352 | 0 | { "nanosecond", 0.000000001 }, |
353 | 0 | { "microsecond", 0.000001 }, |
354 | 0 | { "millisecond", 0.001 }, |
355 | 0 | { "second", 1.0 }, |
356 | 0 | { "minute", 60.0 }, |
357 | 0 | { "hour", 3600.0 }, |
358 | 0 | { "day", 86400.0 }, |
359 | 0 | { "week", 7 * 86400.0 }, |
360 | 0 | { "month", 30 * 86400.0 }, |
361 | 0 | { "year", 365 * 86400.0 }, |
362 | | |
363 | | // Abbreviated forms |
364 | 0 | { "nanosec", 0.000000001 }, |
365 | 0 | { "microsec", 0.000001 }, |
366 | 0 | { "millisec", 0.001 }, |
367 | 0 | { "sec", 1.0 }, |
368 | 0 | { "min", 60.0 }, |
369 | 0 | { "hr", 3600.0 }, |
370 | 0 | { "dy", 86400.0 }, |
371 | 0 | { "wk", 7 * 86400.0 }, |
372 | 0 | { "mon", 30 * 86400.0 }, |
373 | 0 | { "yr", 365 * 86400.0 }, |
374 | | |
375 | | // nano -> n |
376 | 0 | { "nsecond", 0.000000001 }, |
377 | 0 | { "nsec", 0.000000001 }, |
378 | | // micro -> u |
379 | 0 | { "usecond", 0.000001 }, |
380 | 0 | { "usec", 0.000001 }, |
381 | | // milli -> m |
382 | 0 | { "msecond", 0.001 }, |
383 | 0 | { "msec", 0.001 }, |
384 | | |
385 | | // Ultra-short form |
386 | 0 | { "ns", 0.000000001 }, |
387 | 0 | { "us", 0.000001 }, |
388 | 0 | { "ms", 0.001 }, |
389 | 0 | { "s", 1.0 }, |
390 | 0 | { "m", 60.0 }, |
391 | 0 | { "h", 3600.0 }, |
392 | 0 | { "d", 86400.0 }, |
393 | 0 | { "w", 7 * 86400.0 }, |
394 | 0 | { "M", 30 * 86400.0 }, // upper-case M to disambiguate with minute |
395 | 0 | { "y", 365 * 86400.0 } |
396 | 0 | }; |
397 | |
|
398 | 0 | char* unit_start; // Start of unit name. |
399 | 0 | double work_value = 0; |
400 | 0 | int sign = 1; |
401 | 0 | const char* interval_start = SkipLeadingWhiteSpace(str.c_str()); |
402 | 0 | if (*interval_start == '-') { |
403 | 0 | sign = -1; |
404 | 0 | interval_start = SkipLeadingWhiteSpace(interval_start + 1); |
405 | 0 | } else if (*interval_start == '+') { |
406 | 0 | interval_start = SkipLeadingWhiteSpace(interval_start + 1); |
407 | 0 | } |
408 | 0 | if (!*interval_start) { |
409 | | // Empty string and strings with just a sign are illegal. |
410 | 0 | return false; |
411 | 0 | } |
412 | 0 | do { |
413 | | // Leading signs on individual values are not allowed. |
414 | 0 | if (*interval_start == '-' || *interval_start == '+') { |
415 | 0 | return false; |
416 | 0 | } |
417 | 0 | double factor = strtod(interval_start, &unit_start); |
418 | 0 | if (interval_start == unit_start) { |
419 | | // Illegally formatted value, no values consumed by strtod. |
420 | 0 | return false; |
421 | 0 | } |
422 | 0 | unit_start = SkipLeadingWhiteSpace(unit_start); |
423 | 0 | bool found_unit = false; |
424 | 0 | for (size_t i = 0; !found_unit && i < ARRAYSIZE(kUnits); ++i) { |
425 | 0 | const size_t unit_len = strlen(kUnits[i].unit); |
426 | 0 | if (strncmp(unit_start, kUnits[i].unit, unit_len) == 0) { |
427 | 0 | work_value += factor * kUnits[i].seconds; |
428 | 0 | interval_start = unit_start + unit_len; |
429 | | // Allowing pluralization of any unit (except empty string) |
430 | 0 | if (unit_len > 0 && *interval_start == 's') { |
431 | 0 | interval_start++; |
432 | 0 | } |
433 | 0 | found_unit = true; |
434 | 0 | } |
435 | 0 | } |
436 | 0 | if (!found_unit) { |
437 | 0 | return false; |
438 | 0 | } |
439 | 0 | interval_start = SkipLeadingWhiteSpace(interval_start); |
440 | 0 | } while (*interval_start); |
441 | | |
442 | 0 | *value = sign * work_value; |
443 | 0 | return true; |
444 | 0 | } |