fluent-plugin-claymore 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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