wavefront-cli 2.18.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +7 -0
- data/.travis.yml +4 -5
- data/HISTORY.md +20 -1
- data/README.md +79 -29
- data/lib/wavefront-cli/base.rb +26 -2
- data/lib/wavefront-cli/commands/alert.rb +10 -10
- data/lib/wavefront-cli/commands/base.rb +15 -2
- data/lib/wavefront-cli/commands/cloudintegration.rb +3 -3
- data/lib/wavefront-cli/commands/config.rb +2 -1
- data/lib/wavefront-cli/commands/dashboard.rb +8 -6
- data/lib/wavefront-cli/commands/derivedmetric.rb +5 -5
- data/lib/wavefront-cli/commands/event.rb +3 -3
- data/lib/wavefront-cli/commands/integration.rb +5 -5
- data/lib/wavefront-cli/commands/link.rb +3 -3
- data/lib/wavefront-cli/commands/message.rb +2 -2
- data/lib/wavefront-cli/commands/metric.rb +1 -1
- data/lib/wavefront-cli/commands/notificant.rb +3 -3
- data/lib/wavefront-cli/commands/proxy.rb +3 -3
- data/lib/wavefront-cli/commands/query.rb +3 -3
- data/lib/wavefront-cli/commands/savedsearch.rb +3 -3
- data/lib/wavefront-cli/commands/settings.rb +22 -0
- data/lib/wavefront-cli/commands/source.rb +3 -3
- data/lib/wavefront-cli/commands/user.rb +4 -4
- data/lib/wavefront-cli/commands/usergroup.rb +5 -8
- data/lib/wavefront-cli/commands/webhook.rb +3 -3
- data/lib/wavefront-cli/commands/window.rb +3 -3
- data/lib/wavefront-cli/commands/write.rb +6 -4
- data/lib/wavefront-cli/config.rb +14 -0
- data/lib/wavefront-cli/controller.rb +2 -0
- data/lib/wavefront-cli/dashboard.rb +133 -14
- data/lib/wavefront-cli/display/base.rb +28 -7
- data/lib/wavefront-cli/display/dashboard.rb +32 -8
- data/lib/wavefront-cli/display/distribution.rb +4 -1
- data/lib/wavefront-cli/display/printer/long.rb +149 -140
- data/lib/wavefront-cli/display/printer/terse.rb +19 -42
- data/lib/wavefront-cli/display/query.rb +6 -0
- data/lib/wavefront-cli/display/settings.rb +21 -0
- data/lib/wavefront-cli/display/write.rb +12 -5
- data/lib/wavefront-cli/event.rb +1 -1
- data/lib/wavefront-cli/exception.rb +1 -0
- data/lib/wavefront-cli/settings.rb +37 -0
- data/lib/wavefront-cli/stdlib/array.rb +20 -0
- data/lib/wavefront-cli/stdlib/string.rb +8 -0
- data/lib/wavefront-cli/user.rb +1 -1
- data/lib/wavefront-cli/version.rb +1 -1
- data/lib/wavefront-cli/write.rb +310 -10
- data/spec/.rubocop.yml +3 -0
- data/spec/spec_helper.rb +81 -1
- data/spec/wavefront-cli/alert_spec.rb +1 -0
- data/spec/wavefront-cli/cloudintegration_spec.rb +1 -0
- data/spec/wavefront-cli/dashboard_spec.rb +47 -4
- data/spec/wavefront-cli/derivedmetric_spec.rb +1 -0
- data/spec/wavefront-cli/display/base_spec.rb +16 -9
- data/spec/wavefront-cli/display/printer/long_spec.rb +75 -106
- data/spec/wavefront-cli/display/printer/terse_spec.rb +33 -21
- data/spec/wavefront-cli/event_spec.rb +1 -0
- data/spec/wavefront-cli/externallink_spec.rb +1 -0
- data/spec/wavefront-cli/integration_spec.rb +1 -0
- data/spec/wavefront-cli/maintenancewindow_spec.rb +1 -0
- data/spec/wavefront-cli/proxy_spec.rb +2 -0
- data/spec/wavefront-cli/query_spec.rb +9 -0
- data/spec/wavefront-cli/resources/display/alert-human-long +24 -0
- data/spec/wavefront-cli/resources/display/alert-input.json +37 -0
- data/spec/wavefront-cli/resources/display/alerts-human-terse +9 -0
- data/spec/wavefront-cli/resources/display/alerts-input.json +1 -0
- data/spec/wavefront-cli/resources/display/user-human-long +19 -0
- data/spec/wavefront-cli/resources/display/user-human-long-no_sep +18 -0
- data/spec/wavefront-cli/resources/display/user-input.json +1 -0
- data/spec/wavefront-cli/resources/responses/README.md +2 -0
- data/spec/wavefront-cli/resources/responses/alert-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/backups.json +1 -0
- data/spec/wavefront-cli/resources/responses/cloudintegration-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/dashboard-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/derivedmetric-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/event-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/integration-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/link-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/notificant-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/proxy-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/query-cpu.json +1 -0
- data/spec/wavefront-cli/resources/responses/savedsearch-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/user-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/usergroup-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/webhook-list.json +1 -0
- data/spec/wavefront-cli/resources/responses/window-list.json +1 -0
- data/spec/wavefront-cli/savedsearch_spec.rb +1 -0
- data/spec/wavefront-cli/settings_spec.rb +19 -0
- data/spec/wavefront-cli/stdlib/array_spec.rb +20 -0
- data/spec/wavefront-cli/stdlib/string_spec.rb +13 -1
- data/spec/wavefront-cli/user_spec.rb +1 -0
- data/spec/wavefront-cli/usergroup_spec.rb +1 -0
- data/spec/wavefront-cli/webhook_spec.rb +1 -0
- data/spec/wavefront-cli/write_spec.rb +167 -0
- data/wavefront-cli.gemspec +3 -4
- metadata +65 -31
- data/.gitlab-ci.yml +0 -16
- data/lib/wavefront-cli/base_write.rb +0 -298
- data/lib/wavefront-cli/commands/report.rb +0 -37
- data/lib/wavefront-cli/display/printer/base.rb +0 -24
- data/lib/wavefront-cli/display/report.rb +0 -17
- data/lib/wavefront-cli/report.rb +0 -18
- data/spec/wavefront-cli/display/printer/base_spec.rb +0 -20
@@ -1,57 +1,34 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative '../../stdlib/array'
|
2
2
|
|
3
3
|
module WavefrontDisplayPrinter
|
4
4
|
#
|
5
5
|
# Print things which are per-row. The terse listings, primarily
|
6
6
|
#
|
7
|
-
class Terse
|
8
|
-
attr_reader :data, :
|
7
|
+
class Terse
|
8
|
+
attr_reader :data, :fmt
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@
|
10
|
+
# @param data [Hash] data to display, from a response object
|
11
|
+
# @param keys [Array[Symbol]] keys to display, in order
|
12
|
+
#
|
13
|
+
def initialize(data, keys)
|
14
|
+
@data = stringify(data, keys)
|
15
|
+
@fmt = format_string(data, keys)
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
def format_string
|
20
|
-
return '%s' if keys.length == 1
|
21
|
-
lk = longest_keys
|
22
|
-
keys.each_with_object('') { |k, out| out.<< "%-#{lk[k]}s " }
|
18
|
+
def format_string(data, keys)
|
19
|
+
keys.map { |k| "%-#{data.longest_value_of(k)}<#{k}>s" }.join(' ')
|
23
20
|
end
|
24
21
|
|
25
|
-
|
26
|
-
|
27
|
-
#
|
28
|
-
# @return [Hash] with the same keys as :keys and Integer values
|
29
|
-
#
|
30
|
-
# rubocop:disable Metrics/AbcSize
|
31
|
-
def longest_keys
|
32
|
-
keys.each_with_object(Hash[*keys.map { |k| [k, 0] }.flatten]) \
|
33
|
-
do |k, aggr|
|
34
|
-
data.each do |obj|
|
35
|
-
next unless obj.key?(k)
|
36
|
-
val = obj[k]
|
37
|
-
val = val.join(', ') if val.is_a?(Array)
|
38
|
-
aggr[k] = val.size if val.size > aggr[k]
|
39
|
-
end
|
40
|
-
end
|
22
|
+
def stringify(data, keys)
|
23
|
+
data.map { |e| e.tap { keys.each { |k| e[k] = to_list(e[k]) } } }
|
41
24
|
end
|
42
|
-
# rubocop:enable Metrics/AbcSize
|
43
25
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
def prep_output
|
51
|
-
data.each_with_object([]) do |o, aggr|
|
52
|
-
args = keys.map { |k| o[k].is_a?(Array) ? o[k].join(', ') : o[k] }
|
53
|
-
aggr.<< format(fmt_string, *args).rstrip
|
54
|
-
end
|
26
|
+
def to_list(thing)
|
27
|
+
thing.is_a?(Array) ? thing.join(', ') : thing
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
data.map { |e| format(fmt, e).rstrip }.join("\n")
|
55
32
|
end
|
56
33
|
end
|
57
34
|
end
|
@@ -22,6 +22,12 @@ module WavefrontDisplay
|
|
22
22
|
end
|
23
23
|
# rubocop:enable Metrics/AbcSize
|
24
24
|
|
25
|
+
# Prioritizing keys does not make sense in this context
|
26
|
+
#
|
27
|
+
def prioritize_keys(data, _keys)
|
28
|
+
data
|
29
|
+
end
|
30
|
+
|
25
31
|
def mk_timeseries(data)
|
26
32
|
return [] unless data.key?(:timeseries)
|
27
33
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module WavefrontDisplay
|
4
|
+
#
|
5
|
+
# Format human-readable output for external links.
|
6
|
+
#
|
7
|
+
class Settings < Base
|
8
|
+
def do_list_permissions
|
9
|
+
options[:long] ? long_output : multicolumn(:groupName)
|
10
|
+
end
|
11
|
+
|
12
|
+
def do_list_usergroups
|
13
|
+
if options[:long]
|
14
|
+
readable_time_arr(:createdEpochMillis)
|
15
|
+
long_output
|
16
|
+
else
|
17
|
+
multicolumn(:id, :name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -2,21 +2,28 @@ require_relative 'base'
|
|
2
2
|
|
3
3
|
module WavefrontDisplay
|
4
4
|
# Format human-readable output when writing points.
|
5
|
+
# In this context data is a Hash of the form
|
6
|
+
# { sent: 1, rejected: 0, unsent: 0 }
|
5
7
|
#
|
6
8
|
class Write < Base
|
7
|
-
|
9
|
+
attr_reader :not_sent
|
10
|
+
|
8
11
|
def do_point
|
9
|
-
|
10
|
-
|
12
|
+
@not_sent = data['rejected'] + data['unsent']
|
13
|
+
report unless nothing_to_say?
|
14
|
+
exit not_sent.zero? ? 0 : 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def nothing_to_say?
|
18
|
+
options[:quiet] || not_sent.positive?
|
11
19
|
end
|
12
|
-
# rubocop:enable Metrics/AbcSize
|
13
20
|
|
14
21
|
def do_file
|
15
22
|
do_point
|
16
23
|
end
|
17
24
|
|
18
25
|
def report
|
19
|
-
%
|
26
|
+
%w[sent rejected unsent].each do |k|
|
20
27
|
puts format(' %12s %d', k.to_s, data[k])
|
21
28
|
end
|
22
29
|
end
|
data/lib/wavefront-cli/event.rb
CHANGED
@@ -68,7 +68,7 @@ module WavefrontCli
|
|
68
68
|
abort "No locally stored event matches '#{id}'." unless ev
|
69
69
|
|
70
70
|
res = wf.close(ev)
|
71
|
-
ev_file.unlink if ev_file
|
71
|
+
ev_file.unlink if ev_file&.exist? && res.status.code == 200
|
72
72
|
res
|
73
73
|
end
|
74
74
|
# rubocop:enable Metrics/AbcSize
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module WavefrontCli
|
4
|
+
#
|
5
|
+
# CLI coverage for the v2 'settings' API.
|
6
|
+
#
|
7
|
+
class Settings < WavefrontCli::Base
|
8
|
+
def do_list_permissions
|
9
|
+
wf.permissions
|
10
|
+
end
|
11
|
+
|
12
|
+
def do_show_preferences
|
13
|
+
wf.preferences
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_default_usergroups
|
17
|
+
wf.default_user_groups
|
18
|
+
end
|
19
|
+
|
20
|
+
def do_update
|
21
|
+
body = options[:'<key=value>'].each_with_object({}) do |o, a|
|
22
|
+
k, v = o.split('=', 2)
|
23
|
+
next unless v && !v.empty?
|
24
|
+
|
25
|
+
if %w[invitePermissions defaultUserGroups].include?(k)
|
26
|
+
v = v.include?(',') ? v.split(',') : [v]
|
27
|
+
end
|
28
|
+
|
29
|
+
a[k] = v
|
30
|
+
end
|
31
|
+
|
32
|
+
pp body
|
33
|
+
|
34
|
+
wf.update_preferences(body)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Extensions to stdlib's Array
|
2
|
+
#
|
3
|
+
class Array
|
4
|
+
# @return [Integer] the length of the longest string or symbol in
|
5
|
+
# an array
|
6
|
+
#
|
7
|
+
def max_length
|
8
|
+
return 0 if empty?
|
9
|
+
map(&:to_s).map(&:length).max
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Integer] the length of the longest value in an array of
|
13
|
+
# hashes with the given key
|
14
|
+
#
|
15
|
+
# @param key [String, Symbol] key to search for
|
16
|
+
#
|
17
|
+
def longest_value_of(key)
|
18
|
+
map { |v| v[key] }.max_length
|
19
|
+
end
|
20
|
+
end
|
@@ -54,6 +54,14 @@ class String
|
|
54
54
|
tr('^', ' ')
|
55
55
|
end
|
56
56
|
|
57
|
+
# Fold long value lines in two-column output. The returned string
|
58
|
+
# is appended to a key, so the first line is not indented.
|
59
|
+
#
|
60
|
+
def value_fold(indent = 0, twidth = TW)
|
61
|
+
max_line_length = twidth - indent - 4
|
62
|
+
scan_line(max_line_length).join("\n" + ' ' * indent)
|
63
|
+
end
|
64
|
+
|
57
65
|
# @param width [Integer] length of longest string (width of
|
58
66
|
# terminal less some margin)
|
59
67
|
# @return [Array] original string chunked into an array width
|
data/lib/wavefront-cli/user.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
WF_CLI_VERSION = '
|
1
|
+
WF_CLI_VERSION = '3.0.0'.freeze
|
data/lib/wavefront-cli/write.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
require 'wavefront-sdk/support/mixins'
|
2
|
+
require_relative 'base'
|
2
3
|
|
3
4
|
module WavefrontCli
|
4
5
|
#
|
@@ -6,14 +7,39 @@ module WavefrontCli
|
|
6
7
|
# as Report, but has to do a couple of things differently, as it
|
7
8
|
# speaks to a proxy rather than to the API.
|
8
9
|
#
|
9
|
-
class Write <
|
10
|
+
class Write < Base
|
11
|
+
attr_reader :fmt
|
12
|
+
include Wavefront::Mixins
|
13
|
+
SPLIT_PATTERN = /\s(?=(?:[^"]|"[^"]*")*$)/
|
14
|
+
|
15
|
+
# rubocop:disable Metrics/AbcSize
|
16
|
+
def do_point
|
17
|
+
p = { path: options[:'<metric>'],
|
18
|
+
value: options[:'<value>'].delete('\\').to_f }
|
19
|
+
|
20
|
+
tags = tags_to_hash(options[:tag])
|
21
|
+
|
22
|
+
p[:tags] = tags unless tags.empty?
|
23
|
+
p[:source] = options[:host] if options[:host]
|
24
|
+
p[:ts] = parse_time(options[:time]) if options[:time]
|
25
|
+
send_point(p)
|
26
|
+
end
|
27
|
+
# rubocop:enable Metrics/AbcSize
|
28
|
+
|
29
|
+
def do_file
|
30
|
+
valid_format?(options[:infileformat])
|
31
|
+
setup_fmt(options[:infileformat] || 'tmv')
|
32
|
+
process_input(options[:'<file>'])
|
33
|
+
end
|
34
|
+
|
10
35
|
# rubocop:disable Metrics/AbcSize
|
11
36
|
def do_distribution
|
12
37
|
p = { path: options[:'<metric>'],
|
13
38
|
interval: options[:interval] || 'M',
|
14
|
-
value: mk_dist
|
15
|
-
tags: tags_to_hash(options[:tag]) }
|
39
|
+
value: mk_dist }
|
16
40
|
|
41
|
+
tags = tags_to_hash(options[:tag])
|
42
|
+
p[:tags] = tags unless tags.empty?
|
17
43
|
p[:source] = options[:host] if options[:host]
|
18
44
|
p[:ts] = parse_time(options[:time]) if options[:time]
|
19
45
|
send_point(p)
|
@@ -44,7 +70,7 @@ module WavefrontCli
|
|
44
70
|
|
45
71
|
def distribution?
|
46
72
|
return true if options[:distribution]
|
47
|
-
options[:infileformat]
|
73
|
+
options[:infileformat]&.include?('d')
|
48
74
|
end
|
49
75
|
|
50
76
|
def mk_creds
|
@@ -72,11 +98,10 @@ module WavefrontCli
|
|
72
98
|
end
|
73
99
|
|
74
100
|
def validate_opts_file
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
101
|
+
return true if options[:metric] || options[:infileformat]&.include?('m')
|
102
|
+
|
103
|
+
raise(WavefrontCli::Exception::InsufficientData,
|
104
|
+
"Supply a metric path in the file or with '-m'.")
|
80
105
|
end
|
81
106
|
|
82
107
|
def open_connection
|
@@ -86,5 +111,280 @@ module WavefrontCli
|
|
86
111
|
def close_connection
|
87
112
|
wf.close
|
88
113
|
end
|
114
|
+
|
115
|
+
def send_point(point)
|
116
|
+
call_write(point)
|
117
|
+
rescue Wavefront::Exception::InvalidEndpoint
|
118
|
+
abort format("Could not connect to proxy '%s:%s'.",
|
119
|
+
options[:proxy], options[:port])
|
120
|
+
end
|
121
|
+
|
122
|
+
# Read the input, from a file or from STDIN, and turn each line
|
123
|
+
# into Wavefront points.
|
124
|
+
#
|
125
|
+
def process_input(file)
|
126
|
+
if file == '-'
|
127
|
+
read_stdin
|
128
|
+
else
|
129
|
+
call_write(
|
130
|
+
process_input_file(load_data(Pathname.new(file)).split("\n"))
|
131
|
+
)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param data [Array[String]] array of lines
|
136
|
+
#
|
137
|
+
def process_input_file(data)
|
138
|
+
data.each_with_object([]) do |l, a|
|
139
|
+
begin
|
140
|
+
a.<< process_line(l)
|
141
|
+
rescue WavefrontCli::Exception::UnparseableInput => e
|
142
|
+
puts "Bad input. #{e.message}."
|
143
|
+
next
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# A wrapper which lets us send normal points, deltas, or
|
149
|
+
# distributions
|
150
|
+
#
|
151
|
+
def call_write(data, openclose = true)
|
152
|
+
if options[:delta]
|
153
|
+
wf.write_delta(data, openclose)
|
154
|
+
else
|
155
|
+
wf.write(data, openclose)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Read from standard in and stream points through an open
|
160
|
+
# socket. If the user hits ctrl-c, close the socket and exit
|
161
|
+
# politely.
|
162
|
+
#
|
163
|
+
def read_stdin
|
164
|
+
open_connection
|
165
|
+
STDIN.each_line { |l| call_write(process_line(l.strip), false) }
|
166
|
+
close_connection
|
167
|
+
rescue SystemExit, Interrupt
|
168
|
+
puts 'ctrl-c. Exiting.'
|
169
|
+
wf.close
|
170
|
+
exit 0
|
171
|
+
end
|
172
|
+
|
173
|
+
# Find and return the value in a chunked line of input
|
174
|
+
#
|
175
|
+
# param chunks [Array] a chunked line of input from #process_line
|
176
|
+
# return [Float] the value
|
177
|
+
# raise TypeError if field does not exist
|
178
|
+
# raise Wavefront::Exception::InvalidValue if it's not a value
|
179
|
+
#
|
180
|
+
def extract_value(chunks)
|
181
|
+
if fmt.include?('v')
|
182
|
+
v = chunks[fmt.index('v')]
|
183
|
+
v.to_f
|
184
|
+
else
|
185
|
+
raw = chunks[fmt.index('d')].split(',')
|
186
|
+
xpanded = expand_dist(raw)
|
187
|
+
wf.mk_distribution(xpanded)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# We will let users write a distribution as '1 1 1' or '3x1' or
|
192
|
+
# even a mix of the two
|
193
|
+
#
|
194
|
+
def expand_dist(dist)
|
195
|
+
dist.map do |v|
|
196
|
+
if v.is_a?(String) && v.include?('x')
|
197
|
+
x, val = v.split('x', 2)
|
198
|
+
Array.new(x.to_i, val.to_f)
|
199
|
+
else
|
200
|
+
v.to_f
|
201
|
+
end
|
202
|
+
end.flatten
|
203
|
+
end
|
204
|
+
|
205
|
+
# Find and return the source in a chunked line of input.
|
206
|
+
#
|
207
|
+
# @param chunks [Array] a chunked line of input from #process_line
|
208
|
+
# @return [Float] the timestamp, if it is there, or the current
|
209
|
+
# UTC time if it is not.
|
210
|
+
#
|
211
|
+
def extract_ts(chunks)
|
212
|
+
ts = chunks[fmt.index('t')]
|
213
|
+
return parse_time(ts) if valid_timestamp?(ts)
|
214
|
+
rescue TypeError
|
215
|
+
Time.now.utc.to_i
|
216
|
+
end
|
217
|
+
|
218
|
+
# @param chunks [Array] an input line broken into tokens. The
|
219
|
+
# final token will be a space-separated list of point tags.
|
220
|
+
# @return [Hash] of k = v tags.
|
221
|
+
#
|
222
|
+
def extract_tags(chunks)
|
223
|
+
tags_to_hash(chunks.last.split(SPLIT_PATTERN))
|
224
|
+
end
|
225
|
+
|
226
|
+
# Find and return the metric path in a chunked line of input.
|
227
|
+
# The path can be in the data, or passed as an option, or both.
|
228
|
+
# If the latter, then we assume the option is a prefix, and
|
229
|
+
# concatenate the value in the data.
|
230
|
+
#
|
231
|
+
# param chunks [Array] a chunked line of input from #process_line
|
232
|
+
# return [String] the metric path
|
233
|
+
# raise TypeError if field does not exist
|
234
|
+
#
|
235
|
+
def extract_path(chunks)
|
236
|
+
m = chunks[fmt.index('m')]
|
237
|
+
options[:metric] ? [options[:metric], m].join('.') : m
|
238
|
+
rescue TypeError
|
239
|
+
return options[:metric] if options[:metric]
|
240
|
+
raise
|
241
|
+
end
|
242
|
+
|
243
|
+
# Find and return the source in a chunked line of input.
|
244
|
+
#
|
245
|
+
# param chunks [Array] a chunked line of input from #process_line
|
246
|
+
# return [String] the source, if it is there, or if not, the
|
247
|
+
# value passed through by -H, or the local hostname.
|
248
|
+
#
|
249
|
+
def extract_source(chunks)
|
250
|
+
chunks[fmt.index('s')]
|
251
|
+
rescue TypeError
|
252
|
+
options[:source] || Socket.gethostname
|
253
|
+
end
|
254
|
+
|
255
|
+
# Process a line of input, as described by the format string
|
256
|
+
# held in @fmt. Produces a hash suitable for the SDK to send on.
|
257
|
+
#
|
258
|
+
# We let the user define most of the fields, but anything beyond
|
259
|
+
# what they define is always assumed to be point tags. This is
|
260
|
+
# because you can have arbitrarily many of those for each point.
|
261
|
+
#
|
262
|
+
# @param line [String] a line of an input file
|
263
|
+
# @return [Hash]
|
264
|
+
# @raise WavefrontCli::Exception::UnparseableInput if the line
|
265
|
+
# doesn't look right
|
266
|
+
#
|
267
|
+
# rubocop:disable Metrics/AbcSize
|
268
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
269
|
+
def process_line(line)
|
270
|
+
return true if line.empty?
|
271
|
+
chunks = line.split(SPLIT_PATTERN, fmt.length)
|
272
|
+
enough_fields?(line) # can raise exception
|
273
|
+
|
274
|
+
point = { path: extract_path(chunks),
|
275
|
+
value: extract_value(chunks) }
|
276
|
+
|
277
|
+
tags = line_tags(chunks)
|
278
|
+
|
279
|
+
point.tap do |p|
|
280
|
+
p[:tags] = tags unless tags.empty?
|
281
|
+
p[:ts] = extract_ts(chunks) if fmt.include?('t')
|
282
|
+
p[:source] = extract_source(chunks) if fmt.include?('s')
|
283
|
+
p[:interval] = options[:interval] || 'm' if fmt.include?('d')
|
284
|
+
end
|
285
|
+
end
|
286
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
287
|
+
# rubocop:enable Metrics/AbcSize
|
288
|
+
|
289
|
+
# We can get tags from the file, from the -T option, or both.
|
290
|
+
# Merge them, making the -T win if there is a collision.
|
291
|
+
#
|
292
|
+
def line_tags(chunks)
|
293
|
+
file_tags = fmt.last == 'T' ? extract_tags(chunks) : {}
|
294
|
+
opt_tags = tags_to_hash(options[:tag]) || {}
|
295
|
+
file_tags.merge(opt_tags)
|
296
|
+
end
|
297
|
+
|
298
|
+
# Takes an array of key=value tags (as produced by docopt) and
|
299
|
+
# turns it into a hash of key: value tags. Anything not of the
|
300
|
+
# form key=val is dropped. If key or value are quoted, we
|
301
|
+
# remove the quotes.
|
302
|
+
#
|
303
|
+
# @param tags [Array[String]]
|
304
|
+
# @return [Hash] of k: v tags
|
305
|
+
#
|
306
|
+
def tags_to_hash(tags)
|
307
|
+
return nil unless tags
|
308
|
+
|
309
|
+
[tags].flatten.each_with_object({}) do |t, ret|
|
310
|
+
k, v = t.split('=', 2)
|
311
|
+
k.gsub!(/^["']|["']$/, '')
|
312
|
+
ret[k.to_sym] = v.to_s.gsub(/^["']|["']$/, '') if v
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# The format string must contain values. They can be single
|
317
|
+
# values or distributions. So we must have 'v' xor 'd'. It must
|
318
|
+
# not contain anything other than 'm', 't', 'T', 's', 'd', or
|
319
|
+
# 'v', and the 'T', if there, must be at the end. No letter must
|
320
|
+
# appear more than once.
|
321
|
+
#
|
322
|
+
# @param fmt [String] format of input file
|
323
|
+
#
|
324
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
325
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
326
|
+
# rubocop:disable Metrics/AbcSize
|
327
|
+
def valid_format?(fmt)
|
328
|
+
err = if fmt.include?('v') && fmt.include?('d')
|
329
|
+
"'v' and 'd' are mutually exclusive"
|
330
|
+
elsif !fmt.include?('v') && !fmt.include?('d')
|
331
|
+
"format string must include 'v' or 'd'"
|
332
|
+
elsif !fmt.match(/^[dmstTv]+$/)
|
333
|
+
'unsupported field in format string'
|
334
|
+
elsif fmt != fmt.squeeze
|
335
|
+
'repeated field in format string'
|
336
|
+
elsif fmt.include?('T') && !fmt.end_with?('T')
|
337
|
+
"if used, 'T' must come at end of format string"
|
338
|
+
end
|
339
|
+
|
340
|
+
return true if err.nil?
|
341
|
+
|
342
|
+
raise(WavefrontCli::Exception::UnparseableInput, err)
|
343
|
+
end
|
344
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
345
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
346
|
+
# rubocop:enable Metrics/AbcSize
|
347
|
+
|
348
|
+
# Make sure we have the right number of columns, according to
|
349
|
+
# the format string. We want to take every precaution we can to
|
350
|
+
# stop users accidentally polluting their metric namespace with
|
351
|
+
# junk.
|
352
|
+
#
|
353
|
+
# @param line [String] input line
|
354
|
+
# @return [True] if the number of fields is correct
|
355
|
+
# @raise WavefrontCli::Exception::UnparseableInput if there
|
356
|
+
# are not the right number of fields.
|
357
|
+
#
|
358
|
+
def enough_fields?(line)
|
359
|
+
ncols = line.split(SPLIT_PATTERN).length
|
360
|
+
return true if fmt.include?('T') && ncols >= fmt.length
|
361
|
+
return true if ncols == fmt.length
|
362
|
+
raise(WavefrontCli::Exception::UnparseableInput,
|
363
|
+
format('Expected %s fields, got %s', fmt.length, ncols))
|
364
|
+
end
|
365
|
+
|
366
|
+
# Although the SDK does value checking, we'll add another layer
|
367
|
+
# of input checking here. See if the time looks valid. We'll
|
368
|
+
# assume anything before 2000/01/01 or after a year from now is
|
369
|
+
# wrong. Arbitrary, but there has to be a cut-off somewhere.
|
370
|
+
# @param timestamp [String, Integer] epoch timestamp
|
371
|
+
# @return [Bool]
|
372
|
+
#
|
373
|
+
def valid_timestamp?(timestamp)
|
374
|
+
(timestamp.is_a?(Integer) ||
|
375
|
+
timestamp.is_a?(String) && timestamp.match(/^\d+$/)) &&
|
376
|
+
timestamp.to_i > 946_684_800 &&
|
377
|
+
timestamp.to_i < (Time.now.to_i + 31_557_600)
|
378
|
+
end
|
379
|
+
|
380
|
+
def setup_fmt(fmt)
|
381
|
+
@fmt = fmt.split('')
|
382
|
+
end
|
383
|
+
|
384
|
+
def load_data(file)
|
385
|
+
IO.read(file)
|
386
|
+
rescue StandardError
|
387
|
+
raise WavefrontCli::Exception::FileNotFound
|
388
|
+
end
|
89
389
|
end
|
90
390
|
end
|