class Grafana::InfluxDbDatasource

Implements the interface to Prometheus datasources.

Public Class Methods

handles?(model) click to toggle source

@see AbstractDatasource#handles?

# File lib/grafana/influxdb_datasource.rb, line 7
def self.handles?(model)
  tmp = new(model)
  tmp.type == 'influxdb'
end

Public Instance Methods

default_variable_format() click to toggle source

@see AbstractDatasource#default_variable_format

# File lib/grafana/influxdb_datasource.rb, line 51
def default_variable_format
  'regex'
end
raw_query_from_panel_model(panel_query_target) click to toggle source

@see AbstractDatasource#raw_query_from_panel_model

# File lib/grafana/influxdb_datasource.rb, line 43
def raw_query_from_panel_model(panel_query_target)
  return panel_query_target['query'] if panel_query_target['rawQuery']

  # build composed queries
  build_select(panel_query_target['select']) + build_from(panel_query_target) + build_where(panel_query_target['tags']) + build_group_by(panel_query_target['groupBy'])
end
request(query_description) click to toggle source

:database needs to contain the InfluxDb database name :raw_query needs to contain a InfluxDb query as String @see AbstractDatasource#request

# File lib/grafana/influxdb_datasource.rb, line 15
def request(query_description)
  raise MissingSqlQueryError if query_description[:raw_query].nil?

  # replace variables
  query = replace_variables(query_description[:raw_query], query_description[:variables])

  # Unfortunately the grafana internal variables are not replaced in the grafana backend, but in the
  # frontend, i.e. we have to replace them here manually
  # replace $timeFilter variable
  query = query.gsub(/\$timeFilter(?=\W|$)/, "time >= #{query_description[:from]}ms and time <= #{query_description[:to]}ms")

  # replace grafana variables $__interval and $__interval_ms in query
  # TODO: influx datasource currently uses a fixed values of 1000 width for interval variables specified in a query - it should be possible to calculate this according to grafana
  # TODO: check where calculation and replacement of interval variable should take place
  query = query.gsub(/\$(?:__)?interval(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000 / 1000).to_i}s")
  query = query.gsub(/\$(?:__)?interval_ms(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000).to_i}")

  url = "/api/datasources/proxy/#{id}/query?db=#{@model['database']}&q=#{ERB::Util.url_encode(query)}&epoch=ms"

  webrequest = query_description[:prepared_request]
  webrequest.relative_url = url
  webrequest.options.merge!({ request: Net::HTTP::Get })

  result = webrequest.execute(query_description[:timeout])
  preformat_response(result.body)
end

Private Instance Methods

build_from(stmt) click to toggle source
# File lib/grafana/influxdb_datasource.rb, line 89
def build_from(stmt)
  " FROM \"#{"stmt['policy']." unless stmt['policy'] == 'default'}#{stmt['measurement']}\""
end
build_group_by(stmt) click to toggle source
# File lib/grafana/influxdb_datasource.rb, line 57
def build_group_by(stmt)
  groups = []
  fill = ""

  stmt.each do |group|
    case group['type']
    when 'tag'
      groups << "\"#{group['params'].first}\""

    when 'fill'
      fill = " fill(#{group['params'].first})"

    else
      groups << "#{group['type']}(#{group['params'].join(', ')})"

    end
  end

  " GROUP BY #{groups.join(', ')}#{fill}"
end
build_select(stmt) click to toggle source
# File lib/grafana/influxdb_datasource.rb, line 93
def build_select(stmt)
  res = "SELECT"
  parts = []

  stmt.each do |value|
    part = ""

    value.each do |item|
      case item['type']
      when 'field'
        # frame field parameter as string
        part = "\"#{item['params'].first}\""

      when 'alias'
        # append AS with parameter as string
        part = "#{part} AS \"#{item['params'].first}\""


      when 'math'
        # append parameter as raw value for calculation
        part = "#{part} #{item['params'].first}"


      else
        # frame current part by brackets and call by item function including parameters
        part = "#{item['type']}(#{part}#{", #{item['params'].join(', ')}" unless item['params'].empty?})"
      end
    end

    parts << part
  end

  "#{res} #{parts.join(', ')}"
end
build_where(stmt) click to toggle source
# File lib/grafana/influxdb_datasource.rb, line 78
def build_where(stmt)
  custom_where = []

  stmt.each do |where|
    value = where['operator'] =~ /^[=!]~$/ ? where['value'] : "'#{where['value']}'"
    custom_where << "\"#{where['key']}\" #{where['operator']} #{value}"
  end

  " WHERE #{"(#{custom_where.join(' AND ')}) AND " unless custom_where.empty?}$timeFilter"
end
preformat_response(response_body) click to toggle source

@see AbstractDatasource#preformat_response

# File lib/grafana/influxdb_datasource.rb, line 129
def preformat_response(response_body)
  # TODO: how to handle multiple query results?
  json = JSON.parse(response_body)['results'].first['series']
  return {} if json.nil?

  header = ['time']
  content = {}

  # keep sorting, if json has only one target item, otherwise merge results and return
  # as a time sorted array
  return { header: header << "#{json.first['name']} #{json.first['columns'][1]} (#{json.first['tags']})", content: json.first['values'] } if json.length == 1

  # TODO: show warning here, as results may be sorted different
  json.each_index do |i|
    header << "#{json[i]['name']} #{json[i]['columns'][1]} (#{json[i]['tags']})"
    tmp = json[i]['values'].to_h
    tmp.each_key { |key| content[key] = Array.new(json.length) unless content[key] }

    content.merge!(tmp) do |_key, old, new|
      old[i] = new
      old
    end
  end

  { header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
end