fluent-plugin-claymore 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ <source>
2
+ @type tail
3
+ path *.txt
4
+ read_from_head true
5
+ tag claymore.data
6
+ <parse>
7
+ @type claymore
8
+ </parse>
9
+ </source>
10
+
11
+ <match **>
12
+ @type stdout
13
+ </match>
@@ -0,0 +1,29 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'fluent-plugin-claymore'
6
+ spec.version = '1.0.0'
7
+ spec.authors = ['Timothy Stott']
8
+ spec.email = ['stott.timothy@gmail.com']
9
+
10
+ spec.summary = 'Fluentd parser plugin for Claymore Dual Miner logs'
11
+ spec.description = 'Extract time series metrics from Claymore Dual Miner logs'
12
+ spec.homepage = 'https://github.com/timstott/fluent-plugin-claymore'
13
+ spec.license = 'Apache-2.0'
14
+
15
+ test_files, files = `git ls-files -z`.split("\x0").partition do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.files = files
19
+ spec.executables = files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = test_files
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.14'
24
+ spec.add_development_dependency 'rake', '~> 12.0'
25
+ spec.add_development_dependency 'rubocop', '~> 0.50.0'
26
+ spec.add_development_dependency 'test-unit', '~> 3.0'
27
+ spec.add_development_dependency 'timecop', '~> 0.9'
28
+ spec.add_runtime_dependency 'fluentd', ['>= 0.14.10', '< 2']
29
+ end
@@ -0,0 +1,11 @@
1
+ module Claymore
2
+ # Provides method to extract asset symbole
3
+ module AssetSymbol
4
+ ASSET_REGEXP = /(?<asset>DRC|ETH|LBC|PASC|SC)(:| -)/
5
+
6
+ def asset_symbol(line)
7
+ match = line.match(ASSET_REGEXP)
8
+ match && match[:asset]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,55 @@
1
+ require 'claymore/asset_symbol'
2
+
3
+ module Claymore
4
+ # Extracts asset, gpu index and gpu hash rate
5
+ # Sets hash rate to -1 when gpu is off
6
+ #
7
+ # Example input:
8
+ # 05:45:16:028 2100 ETH: GPU0 29.586 Mh/s, GPU1 off
9
+ #
10
+ # Example output:
11
+ # [
12
+ # { 'asset' => 'ETH', 'gpu' => 0, 'hash_rate' => 29.586, 'type' => 'GPU_HASH_RATE' },
13
+ # { 'asset' => 'ETH', 'gpu' => 1, 'hash_rate' => -1.0, 'type' => 'GPU_HASH_RATE' }
14
+ # ]
15
+ class GPUHashRate
16
+ include AssetSymbol
17
+
18
+ RATES_REGEXP = %r{GPU(?<index>\d+) (?<rate>\d+(?:\.\d+)? Mh\/s|off)}
19
+ LINE_REGEXP = Regexp.new("#{ASSET_REGEXP.source} #{RATES_REGEXP.source}")
20
+
21
+ def self.call(line)
22
+ new(line).call
23
+ end
24
+
25
+ attr_reader :line
26
+
27
+ def initialize(line)
28
+ @line = line
29
+ end
30
+
31
+ # rubocop:disable Metrics/MethodLength
32
+ def call
33
+ (match = LINE_REGEXP.match(line)) || return
34
+
35
+ raw_rates.each_with_object([]) do |(raw_index, raw_rate), acc|
36
+ hash_rate = raw_rate == 'off' ? -1.0 : raw_rate.to_f.round(3)
37
+ index = raw_index.to_i
38
+
39
+ acc << {
40
+ 'type' => 'GPU_HASH_RATE',
41
+ 'asset' => match[:asset],
42
+ 'gpu' => index,
43
+ 'hash_rate' => hash_rate
44
+ }
45
+ end
46
+ end
47
+ # rubocop:enable Metrics/MethodLength
48
+
49
+ private
50
+
51
+ def raw_rates
52
+ line.scan(RATES_REGEXP)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ require 'claymore/asset_symbol'
2
+
3
+ module Claymore
4
+ # Extracts total hash rate with asset symbol
5
+ #
6
+ # Example input:
7
+ # 05:45:16:028 2100 ETH - Total Speed: 90.118 Mh/s, Total Shares: 237, Rejected: 0, Time: 06:50
8
+ #
9
+ # Example output:
10
+ # { 'asset' => 'ETH', 'hash_rate' => 90.118, 'type' => 'TOTAL_HASH_RATE' }
11
+ class TotalHashRate
12
+ include AssetSymbol
13
+
14
+ TOTAL_RATE_REGEXP = %r{Total Speed: (?<rate>\d+\.\d+ Mh\/s)}
15
+ LINE_REGEXP = Regexp.new("#{ASSET_REGEXP.source} #{TOTAL_RATE_REGEXP.source}")
16
+
17
+ def self.call(line)
18
+ new(line).call
19
+ end
20
+
21
+ attr_reader :line
22
+
23
+ def initialize(line)
24
+ @line = line
25
+ end
26
+
27
+ def call
28
+ (match = LINE_REGEXP.match(line)) || return
29
+
30
+ {
31
+ 'asset' => match[:asset],
32
+ 'hash_rate' => match[:rate].to_f.round(3),
33
+ 'type' => 'TOTAL_HASH_RATE'
34
+ }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,70 @@
1
+ require 'fluent/plugin/parser'
2
+ require 'claymore/gpu_hash_rate'
3
+ require 'claymore/total_hash_rate'
4
+
5
+ module Fluent
6
+ module Plugin
7
+ class ClaymoreParser < Fluent::Plugin::Parser
8
+ Fluent::Plugin.register_parser('claymore', self)
9
+
10
+ # Extract gpu index, temperature, old and new fan speed
11
+ #
12
+ # Example:
13
+ # 09:27:02:820 1834 GPU 4 temp = 45, old fan speed = 0, new fan speed = 75
14
+ # { 'gpu' => 4, 'old_fan' => 0, 'new_fan' => 75, 'temperature' => 45 }
15
+ GPU_TEMP = lambda do |text|
16
+ match = text.match(/GPU (?<gpu>\d) temp = (?<temperature>\d+), old.+= (?<old_fan>\d+), new.+= (?<new_fan>\d+)/)
17
+ match.names.zip(match.captures).map { |(k, v)| [k, v.to_i] }.push(%w[type GPU_TEMP]).to_h if match
18
+ end
19
+
20
+ # Extract gpu share found
21
+ #
22
+ # Example:
23
+ # 11:04:02:920 234c ETH: 12/17/17-11:04:02 - SHARE FOUND - (GPU 5)
24
+ # { 'asset' => 'ETH', 'gpu' => 5, 'share_found' => 1 }
25
+ GPU_SHARE_FOUND = lambda do |text|
26
+ match = text.match(/(?<asset>[A-Z]{2,}):.+SHARE FOUND.+\(GPU (?<gpu>\d+)/)
27
+ { 'type' => 'GPU_SHARE_FOUND', 'asset' => match[:asset], 'gpu' => match[:gpu].to_i, 'count' => 1 } if match
28
+ end
29
+
30
+ # Extract connection lost
31
+ #
32
+ # Example:
33
+ # 20:15:08:451 2338 ETH: Connection lost, retry in 20 sec..
34
+ # { 'asset' => 'ETH', 'connection_lost' => 1 }
35
+ CONNECTION_LOST = lambda do |text|
36
+ match = text.match(/(?<asset>[A-Z]{2,}):.+Connection lost/)
37
+ { 'type' => 'CONNECTION_LOST', 'asset' => match[:asset], 'count' => 1 } if match
38
+ end
39
+
40
+ INCORRECT_SHARE = lambda do |text|
41
+ match = text.match(/GPU #(?<gpu>\d+) got incorrect share/)
42
+ { 'type' => 'INCORRECT_SHARE', 'gpu' => match[:gpu].to_i, 'count' => 1 } if match
43
+ end
44
+
45
+ EXTRACTORS = [
46
+ CONNECTION_LOST,
47
+ Claymore::GPUHashRate,
48
+ Claymore::TotalHashRate,
49
+ GPU_SHARE_FOUND,
50
+ GPU_TEMP,
51
+ INCORRECT_SHARE
52
+ ].freeze
53
+
54
+ def parse(text)
55
+ EXTRACTORS
56
+ .map { |extractor| extractor.call(text) }
57
+ .reject { |result| result.nil? || result.empty? }
58
+ .compact
59
+ .flatten
60
+ .each { |result| yield time, result }
61
+ end
62
+
63
+ # Claymore filename include the date however,
64
+ # Claymore log line include time without date
65
+ def time
66
+ parse_time(nil)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,43 @@
1
+ require 'helper'
2
+ require 'claymore/gpu_hash_rate.rb'
3
+
4
+ class GPUHashRateTest < Test::Unit::TestCase
5
+ test 'return nil when no match' do
6
+ assert_nil service('ETH: GPU0')
7
+ assert_nil service('GPU0 24.1 Mh/s')
8
+ assert_nil service('SC - Total Speed: 292.684 Mh/s')
9
+ end
10
+
11
+ test 'extracts individual GPU hash rates when available' do
12
+ text = 'ETH: GPU0 24.314 Mh/s, GPU1 24.01 Mh/s, GPU20 24.1 Mh/s'
13
+ assert_equal service(text), [
14
+ { 'asset' => 'ETH', 'gpu' => 0, 'hash_rate' => 24.314, 'type' => 'GPU_HASH_RATE' },
15
+ { 'asset' => 'ETH', 'gpu' => 1, 'hash_rate' => 24.01, 'type' => 'GPU_HASH_RATE' },
16
+ { 'asset' => 'ETH', 'gpu' => 20, 'hash_rate' => 24.1, 'type' => 'GPU_HASH_RATE' }
17
+ ]
18
+ end
19
+
20
+ test 'extracts asset name' do
21
+ text = 'SC: GPU0 292.862 Mh/s'
22
+ assert_equal service(text), [
23
+ { 'asset' => 'SC', 'gpu' => 0, 'hash_rate' => 292.862, 'type' => 'GPU_HASH_RATE' }
24
+ ]
25
+ end
26
+
27
+ test 'assigns -1 hash rate value when GPU is off' do
28
+ text = 'ETH: GPU0 29 Mh/s, GPU1 off'
29
+ assert_equal service(text), [
30
+ { 'asset' => 'ETH', 'gpu' => 0, 'hash_rate' => 29, 'type' => 'GPU_HASH_RATE' },
31
+ { 'asset' => 'ETH', 'gpu' => 1, 'hash_rate' => -1.0, 'type' => 'GPU_HASH_RATE' }
32
+ ]
33
+
34
+ text = 'LBC: GPU0 off'
35
+ assert_equal service(text), [
36
+ { 'asset' => 'LBC', 'gpu' => 0, 'hash_rate' => -1.0, 'type' => 'GPU_HASH_RATE' }
37
+ ]
38
+ end
39
+
40
+ def service(text)
41
+ Claymore::GPUHashRate.call(text)
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ require 'helper'
2
+ require 'claymore/total_hash_rate.rb'
3
+
4
+ class TotalHashRateTest < Test::Unit::TestCase
5
+ test 'return nil when no match' do
6
+ assert_nil service('ETH: job is the same')
7
+ assert_nil service('ETH: checking pool connection...')
8
+ assert_nil service('ETH: GPU0 29 Mh/s, GPU1 off')
9
+ end
10
+
11
+ test 'extracts asset name and total hash rate' do
12
+ line = 'ETH - Total Speed: 90.118 Mh/s, Total Shares: 237, Rejected: 0, Time: 06:50'
13
+ assert_equal ({
14
+ 'asset' => 'ETH',
15
+ 'hash_rate' => 90.118,
16
+ 'type' => 'TOTAL_HASH_RATE'
17
+ }), service(line)
18
+ end
19
+
20
+ def service(line)
21
+ Claymore::TotalHashRate.call(line)
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../../', __FILE__))
2
+ require 'test-unit'
3
+ require 'timecop'
4
+ require 'fluent/test'
5
+ require 'fluent/test/driver/parser'
6
+ require 'fluent/test/helpers'
7
+
8
+ Test::Unit::TestCase.include(Fluent::Test::Helpers)
9
+ Test::Unit::TestCase.extend(Fluent::Test::Helpers)
@@ -0,0 +1,82 @@
1
+ require 'helper'
2
+ require 'fluent/plugin/parser_claymore.rb'
3
+
4
+ class ClaymoreParserTest < Test::Unit::TestCase
5
+ setup do
6
+ Fluent::Test.setup
7
+ end
8
+
9
+ test 'uses current Time instead of file/line time' do
10
+ t1 = '2017-12-20T07:30:05.123+00:00'
11
+ Timecop.freeze(t1) do
12
+ parse('') do |time, _record|
13
+ assert_equal time, Time.parse(t1)
14
+ end
15
+ end
16
+ end
17
+
18
+ test 'returns nothing when no match' do
19
+ parse('blahblah') do |_time, record|
20
+ assert_nil record
21
+ end
22
+ end
23
+
24
+ test 'extracts gpu hash rates' do
25
+ line = '17:22:59:067 25e8 ETH: GPU0 24.314 Mh/s, GPU1 24.01 Mh/s, GPU2 24.1 Mh/s'
26
+ records = []
27
+ parse(line) { |_time, record| records.push record }
28
+ assert_equal records, [
29
+ { 'asset' => 'ETH', 'gpu' => 0, 'hash_rate' => 24.314, 'type' => 'GPU_HASH_RATE' },
30
+ { 'asset' => 'ETH', 'gpu' => 1, 'hash_rate' => 24.01, 'type' => 'GPU_HASH_RATE' },
31
+ { 'asset' => 'ETH', 'gpu' => 2, 'hash_rate' => 24.1, 'type' => 'GPU_HASH_RATE' }
32
+ ]
33
+ end
34
+
35
+ test 'extracts gpu hash rates with off gpus' do
36
+ line = '1559:05:45:16:028 2100 ETH: GPU0 29.586 Mh/s, GPU1 off'
37
+ records = []
38
+ parse(line) { |_time, record| records.push record }
39
+ assert_equal records, [
40
+ { 'asset' => 'ETH', 'gpu' => 0, 'hash_rate' => 29.586, 'type' => 'GPU_HASH_RATE' },
41
+ { 'asset' => 'ETH', 'gpu' => 1, 'hash_rate' => -1, 'type' => 'GPU_HASH_RATE' }
42
+ ]
43
+ end
44
+
45
+ test 'extracts gpu temperature, old and new fan speed' do
46
+ line = '09:27:02:820 1834 GPU 4 temp = 45, old fan speed = 0, new fan speed = 75'
47
+ parse(line) do |_time, record|
48
+ assert_equal record, 'gpu' => 4, 'old_fan' => 0, 'new_fan' => 75, 'temperature' => 45, 'type' => 'GPU_TEMP'
49
+ end
50
+ end
51
+
52
+ test 'extracts gpu share found' do
53
+ line = '11:04:02:920 234c ETH: 12/17/17-11:04:02 - SHARE FOUND - (GPU 5)'
54
+ parse(line) do |_time, record|
55
+ assert_equal record, 'asset' => 'ETH', 'gpu' => 5, 'count' => 1, 'type' => 'GPU_SHARE_FOUND'
56
+ end
57
+ end
58
+
59
+ test 'extracts connection lost' do
60
+ line = '20:15:08:451 2338 ETH: Connection lost, retry in 20 sec...'
61
+ parse(line) do |_time, record|
62
+ assert_equal record, 'asset' => 'ETH', 'count' => 1, 'type' => 'CONNECTION_LOST'
63
+ end
64
+ end
65
+
66
+ test 'extracts incorrect share' do
67
+ line = '05:07:23:959 acc GPU #3 got incorrect share. If you see this warning often, make sure you did not overclock it too much!'
68
+ parse(line) do |_time, record|
69
+ assert_equal record, 'gpu' => 3, 'count' => 1, 'type' => 'INCORRECT_SHARE'
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def parse(txt, &block)
76
+ create_driver({}).instance.parse(txt, &block)
77
+ end
78
+
79
+ def create_driver(conf)
80
+ Fluent::Test::Driver::Parser.new(Fluent::Plugin::ClaymoreParser).configure(conf)
81
+ end
82
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-claymore
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Timothy Stott
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.50.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.50.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: test-unit
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: timecop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: fluentd
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 0.14.10
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: '2'
93
+ type: :runtime
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 0.14.10
100
+ - - "<"
101
+ - !ruby/object:Gem::Version
102
+ version: '2'
103
+ description: Extract time series metrics from Claymore Dual Miner logs
104
+ email:
105
+ - stott.timothy@gmail.com
106
+ executables: []
107
+ extensions: []
108
+ extra_rdoc_files: []
109
+ files:
110
+ - ".rubocop.yml"
111
+ - ".rubocop_todo.yml"
112
+ - Gemfile
113
+ - Gemfile.lock
114
+ - LICENSE
115
+ - README.md
116
+ - Rakefile
117
+ - circle.yml
118
+ - example/1516312455_log.txt
119
+ - example/fluentd.conf
120
+ - fluent-plugin-claymore.gemspec
121
+ - lib/claymore/asset_symbol.rb
122
+ - lib/claymore/gpu_hash_rate.rb
123
+ - lib/claymore/total_hash_rate.rb
124
+ - lib/fluent/plugin/parser_claymore.rb
125
+ - test/claymore/test_gpu_hash_rate.rb
126
+ - test/claymore/test_total_hash_rate.rb
127
+ - test/helper.rb
128
+ - test/plugin/test_parser_claymore.rb
129
+ homepage: https://github.com/timstott/fluent-plugin-claymore
130
+ licenses:
131
+ - Apache-2.0
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubyforge_project:
149
+ rubygems_version: 2.6.14
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Fluentd parser plugin for Claymore Dual Miner logs
153
+ test_files:
154
+ - test/claymore/test_gpu_hash_rate.rb
155
+ - test/claymore/test_total_hash_rate.rb
156
+ - test/helper.rb
157
+ - test/plugin/test_parser_claymore.rb