|
| 1 | +#!/opt/puppetlabs/puppet/bin/ruby |
| 2 | + |
| 3 | +require 'net/https' |
| 4 | +require 'json' |
| 5 | +require 'uri' |
| 6 | +require 'time' |
| 7 | +require 'optparse' |
| 8 | +require 'yaml' |
| 9 | + |
| 10 | +options = {} |
| 11 | + |
| 12 | +OptionParser.new do |opts| |
| 13 | + opts.banner = "Usage: #{File.basename(__FILE__)} [options]" |
| 14 | + opts.on('-p', '--[no-]print', 'Print to STDOUT') { |p| options[:print] = p } |
| 15 | + opts.on('-m [TYPE]', '--metrics_type [TYPE]', 'Type of metrics to collect') { |v| options[:metrics_type] = v } |
| 16 | + opts.on('-o [DIR]', '--output_dir [DIR]', 'Directory to save output to') { |o| options[:output_dir] = o } |
| 17 | + opts.on('--metrics_port [PORT]', 'The port the metrics service runs on') { |port| options[:metrics_port] = port } |
| 18 | + opts.on('--[no-]ssl', 'Use SSL when collecting metrics') { |ssl| options[:ssl] = ssl } |
| 19 | +end.parse! |
| 20 | + |
| 21 | +if options[:metrics_type].nil? then |
| 22 | + STDERR.puts '--metrics_type (-m) is a required argument' |
| 23 | + exit 1 |
| 24 | +end |
| 25 | + |
| 26 | +# Allow scripts that require this script to access the options hash. |
| 27 | +OPTIONS = options |
| 28 | + |
| 29 | +config_file = File.expand_path("../../config/#{options[:metrics_type]}.yaml", __FILE__) |
| 30 | +config = YAML.load_file(config_file) |
| 31 | + |
| 32 | +def coalesce(higher_precedence, lower_precedence, default = nil) |
| 33 | + [higher_precedence, lower_precedence].find{|x|!x.nil?} || default |
| 34 | +end |
| 35 | + |
| 36 | +METRICS_TYPE = options[:metrics_type] |
| 37 | +OUTPUT_DIR = options[:output_dir] |
| 38 | +PE_VERSION = config['pe_version'] |
| 39 | +CERTNAME = config['clientcert'] |
| 40 | +HOSTS = config['hosts'] |
| 41 | +PORT = coalesce(options[:metrics_port], config['metrics_port']) |
| 42 | +USE_SSL = coalesce(options[:ssl], config['ssl'], true) |
| 43 | +EXCLUDES = config['excludes'] |
| 44 | +ADDITIONAL_METRICS = config['additional_metrics'] |
| 45 | + |
| 46 | +# Metrics endpoints for our Puma services require a client certificate with SSL. |
| 47 | +# Metrics endpoints for our Trapper Keeper services do not require a client certificate. |
| 48 | + |
| 49 | +if USE_CLIENTCERT |
| 50 | + SSLDIR = `/opt/puppetlabs/bin/puppet config print ssldir`.chomp |
| 51 | +end |
| 52 | + |
| 53 | +$error_array = [] |
| 54 | + |
| 55 | +def generate_host_url(host, port, use_ssl) |
| 56 | + protocol = use_ssl ? 'https' : 'http' |
| 57 | + |
| 58 | + host_url = "#{protocol}://#{host}:#{port}" |
| 59 | +end |
| 60 | + |
| 61 | +def setup_connection(url, use_ssl) |
| 62 | + uri = URI.parse(url) |
| 63 | + http = Net::HTTP.new(uri.host, uri.port) |
| 64 | + |
| 65 | + if use_ssl then |
| 66 | + http.use_ssl = true |
| 67 | + http.verify_mode = OpenSSL::SSL::VERIFY_NONE |
| 68 | + |
| 69 | + if USE_SSL && USE_CLIENTCERT |
| 70 | + # PE Puma services serve metrics from endpoints requiring a client certificate. |
| 71 | + # If https://github.com/puma/puma/pull/2098 is merged into the Puma used by PE, |
| 72 | + # we can collect metrics from /stats and /gc-stats without a client certificate. |
| 73 | + http.ca_path = "#{SSLDIR}/ca" |
| 74 | + http.ca_file = "#{SSLDIR}/certs/ca.pem" |
| 75 | + http.cert = OpenSSL::X509::Certificate.new(File.read("#{SSLDIR}/certs/#{CERTNAME}.pem")) |
| 76 | + http.key = OpenSSL::PKey::RSA.new(File.read("#{SSLDIR}/private_keys/#{CERTNAME}.pem")) |
| 77 | + end |
| 78 | + end |
| 79 | + |
| 80 | + return http, uri |
| 81 | +end |
| 82 | + |
| 83 | +def get_endpoint(url, use_ssl) |
| 84 | + http, uri = setup_connection(url, use_ssl) |
| 85 | + |
| 86 | + data = JSON.parse(http.get(uri.request_uri).body) |
| 87 | +rescue Exception => e |
| 88 | + $error_array << "#{e}" |
| 89 | + data = {} |
| 90 | +end |
| 91 | + |
| 92 | +def post_endpoint(url, use_ssl, post_data) |
| 93 | + http, uri = setup_connection(url, use_ssl) |
| 94 | + |
| 95 | + request = Net::HTTP::Post.new(uri.request_uri) |
| 96 | + request.content_type = 'application/json' |
| 97 | + request.body = post_data |
| 98 | + |
| 99 | + data = JSON.parse(http.request(request).body) |
| 100 | +rescue Exception => e |
| 101 | + $error_array << "#{e}" |
| 102 | + data = {} |
| 103 | +end |
| 104 | + |
| 105 | +def individually_retrieve_additional_metrics(host, port, use_ssl, metrics) |
| 106 | + host_url = generate_host_url(host, port, use_ssl) |
| 107 | + |
| 108 | + metrics_array = [] |
| 109 | + metrics.each do |metric| |
| 110 | + endpoint = URI.escape("#{host_url}/metrics/v1/mbeans/#{metric['url']}") |
| 111 | + metrics_array << { 'name' => metric['name'], 'data' => get_endpoint(endpoint, use_ssl) } |
| 112 | + end |
| 113 | + |
| 114 | + return metrics_array |
| 115 | +end |
| 116 | + |
| 117 | +def bulk_retrieve_additional_metrics(host, port, use_ssl, metrics) |
| 118 | + host_url = generate_host_url(host, port, use_ssl) |
| 119 | + |
| 120 | + post_data = [] |
| 121 | + metrics.each do |metric| |
| 122 | + post_data << metric['url'] |
| 123 | + end |
| 124 | + |
| 125 | + endpoint = "#{host_url}/metrics/v1/mbeans" |
| 126 | + metrics_output = post_endpoint(endpoint, use_ssl, post_data.to_json) |
| 127 | + metrics_array = [] |
| 128 | + |
| 129 | + metrics.each_index do |index| |
| 130 | + metric_name = metrics[index]['name'] |
| 131 | + metric_data = metrics_output[index] |
| 132 | + metrics_array << { 'name' => metric_name, 'data' => metric_data } |
| 133 | + end |
| 134 | + |
| 135 | + return metrics_array |
| 136 | +end |
| 137 | + |
| 138 | +def retrieve_additional_metrics(host, port, use_ssl, pe_version, metrics) |
| 139 | + if Gem::Version.new(pe_version) < Gem::Version.new('2016.2.0') then |
| 140 | + metrics_array = individually_retrieve_additional_metrics(host, port, use_ssl, metrics) |
| 141 | + else |
| 142 | + metrics_array = bulk_retrieve_additional_metrics(host, port, use_ssl, metrics) |
| 143 | + end |
| 144 | + |
| 145 | + return metrics_array |
| 146 | +end |
| 147 | + |
| 148 | +def filter_metrics(dataset, filters) |
| 149 | + return dataset if filters.empty? |
| 150 | + |
| 151 | + case dataset |
| 152 | + when Hash |
| 153 | + dataset = dataset.inject({}) {|m, (k, v)| m[k] = filter_metrics(v,filters) unless filters.include? k ; m } |
| 154 | + when Array |
| 155 | + dataset.map! {|e| filter_metrics(e,filters)} |
| 156 | + end |
| 157 | + |
| 158 | + return dataset |
| 159 | +end |
0 commit comments