Class: Avalon::Miner

Inherits:
Node
  • Object
show all
Extended by:
Extractable
Defined in:
lib/avalon/miner.rb

Overview

Miner is a node encapsulating a single Avalon unit

Constant Summary collapse

FIELDS =

Field formats: name => [width, pattern, type/conversion]

{
  :unit => [6, /(?<=MHS av=)[\d\.]*/, :i],
  :pool => [6, /./, nil], # not in miner status string...
  :ping => [6, /./, nil],  # not in miner status string...
  :rst => [3, /./, nil],  # not in miner status string...
  :uptime => [9, /(?<=Elapsed=)[\d\.]*/, ->(x){ my_time(x, :relative_time)}],
  :last => [8, /(?<=Status=Alive,).*?Last Share Time=[\d\.]*/,
            ->(x){ convert_last(x)}],
  :miner => [5, /(?<=Description=cgminer )[\d\.]*/, :s],
  :freq => [4, /(?<=frequency=)[\d\.]*/, :i],
  :'°C' => [2, /(?<=Temperature=)[\d\.]*/, :i],
  :fan2 => [4, /(?<=fan2=)[\d\.]*/, :i],
  :fan3 => [4, /(?<=fan3=)[\d\.]*/, :i],
  :WU => [4, /(?<=,Work Utility=)[\d\.]*/, :i],
  :getwork => [7, /(?<=Getworks=)[\d\.]*/, :i],
  :accept => [6, /(?<=,Accepted=)[\d\.]*/, :i],
  :reject => [6, /(?<=Rejected=)[\d\.]*/, :i],
  :stale => [5, /(?<=Stale=)[\d\.]*/, :i],
  :error => [6, /(?<=Hardware Errors=)[\d\.]*/, :i],
  # :block => [5, /(?<=Network Blocks=)[\d\.]*/, :i],
  #      :found => [2, /(?<=Found Blocks=)[\d\.]*/, :i],
}

Instance Attribute Summary

Attributes inherited from Node

#data, #ip

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Extractable

extract_data_from, my_time, print_headers

Methods inherited from Node

#[], #[]=, create, #num

Methods included from Utils

#alarm, #duration, #find_file, #ping, #play, #system

Constructor Details

#initialize(monitor, ip, min_mhs, worker_name = nil) ⇒ Miner

Returns a new instance of Miner.



56
57
58
59
60
61
62
# File 'lib/avalon/miner.rb', line 56

def initialize monitor, ip, min_mhs, worker_name=nil
  @ip, @min_mhs, @worker_name = ip, min_mhs*1000 , worker_name
  @monitor = monitor
  @config = Avalon::Config.config # TODO: monitor.config?
  @fails = 0
  super()
end

Class Method Details

.convert_last(x) ⇒ Object

Last share converter (Miner-specific)



41
42
43
44
45
46
47
48
49
# File 'lib/avalon/miner.rb', line 41

def self.convert_last x
  y = x[/(?<=Last Share Time=)[\d\.]*/]

  if y.nil? || y == '0'
    "never"
  else
    my_time(Time.now.getgm.to_i-y.to_i, :relative_time)
  end
end


51
52
53
54
# File 'lib/avalon/miner.rb', line 51

def self.print_headers
  puts "\nMiner status as of #{Time.now.getlocal.asctime}:\nmhs: " +
    FIELDS.map {|name, (width,_,_ )| name.to_s.rjust(width)}.join(' ')
end

Instance Method Details

#get_api(call) ⇒ Object



64
65
66
# File 'lib/avalon/miner.rb', line 64

def get_api call
  self[:ping] ? `bash -ic "echo -n '#{call}' | nc #{@ip} 4028"` : ""
end

#lastObject



97
98
99
# File 'lib/avalon/miner.rb', line 97

def last
  duration(self[:last])
end

#poll(verbose = true) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/avalon/miner.rb', line 68

def poll verbose=true
  self[:ping] = ping @ip

  status = get_api('summary') + get_api('pools') + get_api('devs') + get_api('stats')
  @poll_time = Time.now
  # p get_api('summary')

  data = self.class.extract_data_from(status)

  if data.empty?
    @data = {:ping => self[:ping], :rst => self[:rst]}
  else
    @data.merge! data
    if @config[:monitor][:per_hour]
      [:getwork, :accept, :reject, :stale, :error].each do |key|
        self[key] = (self[key]/upminutes*60).round(1) if self[key]
      end
    end
  end

  self[:pool] = pool_hash

  puts "#{self}" if verbose
end

#pool_hashObject



113
114
115
116
117
# File 'lib/avalon/miner.rb', line 113

def pool_hash
  if @monitor.pool && @worker_name && @monitor.pool[:workers] && @monitor.pool[:workers][@worker_name]
    @monitor.pool[:workers][@worker_name][:hash_rate].round(0)
  end
end

#reportObject

Check for any exceptional situations in stats, sound alarm if any



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/avalon/miner.rb', line 120

def report
  if data[:ping].nil? || data[:unit].nil?
    @fails += 1
    if @fails >= @config[:alert_after]
      alarm "Miner #{num} did not respond to status query", :failure
    end
  else
    @fails = 0
    @last_restart ||= restart_time

    # Detect Miner reset correctly
    if (restart_time - @last_restart) > 20
      @last_restart = restart_time
      self[:rst] = (self[:rst] || 0) + 1
      alarm "Miner #{num} restarted", :restart
    elsif unit_hash == 0 && last == 'never' && temp == 0
      alarm "Miner #{num} is stuck in error state!!!", :failure
    elsif upminutes > 5 # Miner settled down
      if unit_hash < @min_mhs
        alarm "Miner #{num} performance is #{unit_hash}, should be #{@min_mhs}", :perf_low
      elsif last == 'never' || last > @config[:alert_last_share]
        alarm "Miner #{num} last shares was #{last} min ago", :last_share
      elsif temp && temp >= @config[:alert_temp_high]
        alarm "Miner #{num} too hot at #{temp}°C, needs cooling", :temp_high
      elsif self[:freq] && temp && temp <= @config[:alert_temp_low]
        alarm "Miner #{num} temp low at #{temp}°C, is it hashing at all?", :temp_low
      end
    end
  end
end

#resetObject

Reset or reboot Miner



152
153
154
# File 'lib/avalon/miner.rb', line 152

def reset
  `ssh root@#{ip} "reboot"`
end

#restart_timeObject



101
102
103
# File 'lib/avalon/miner.rb', line 101

def restart_time
  @poll_time - upminutes * 60.0
end

#tempObject



105
106
107
# File 'lib/avalon/miner.rb', line 105

def temp
  self[:'°C']
end

#to_sObject



156
157
158
159
# File 'lib/avalon/miner.rb', line 156

def to_s
  num.to_s.rjust(3) + ": " + 
    FIELDS.map {|key, (width, _, _ )| @data[key].to_s.rjust(width)}.join(" ")
end

#unit_hashObject



109
110
111
# File 'lib/avalon/miner.rb', line 109

def unit_hash
  self[:unit] || 0
end

#upminutesObject



93
94
95
# File 'lib/avalon/miner.rb', line 93

def upminutes
  duration(self[:uptime])
end