github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/tests/plugins/cgminer/cgminer_test.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package cgminer
    20  
    21  import (
    22  	"context"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/e154/smart-home/adaptors"
    27  	"github.com/e154/smart-home/common"
    28  	"github.com/e154/smart-home/common/debug"
    29  	"github.com/e154/smart-home/common/events"
    30  	m "github.com/e154/smart-home/models"
    31  	"github.com/e154/smart-home/plugins/cgminer"
    32  	"github.com/e154/smart-home/system/bus"
    33  	"github.com/e154/smart-home/system/scripts"
    34  	"github.com/e154/smart-home/system/supervisor"
    35  	. "github.com/e154/smart-home/tests/plugins"
    36  	. "github.com/smartystreets/goconvey/convey"
    37  )
    38  
    39  func TestCgMiner(t *testing.T) {
    40  
    41  	const cgminerSourceScript = `
    42  
    43  ifError =(res)->
    44      return !res || res.error || res.Error
    45  
    46  checkStatus =->
    47      stats = Miner.stats()
    48      if ifError(stats)
    49          EntitySetStateName ENTITY_ID, 'ERROR'
    50          return
    51      p = unmarshal stats.result
    52      attrs = {
    53          heat: false
    54          chain1_temp_chip: p.temp2_1
    55          chain2_temp_chip: p.temp2_2
    56          chain3_temp_chip: p.temp2_3
    57          chain4_temp_chip: p.temp2_4
    58          chain1_temp_pcb: p.temp1
    59          chain2_temp_pcb: p.temp2
    60          chain3_temp_pcb: p.temp3
    61          chain4_temp_pcb: p.temp4
    62          fan1: p.fan1
    63          fan2: p.fan2
    64      }
    65  
    66      EntitySetState ENTITY_ID,
    67          new_state: 'ENABLED'
    68          attribute_values: attrs
    69          storage_save: true
    70  
    71  checkSum =->
    72      summary = Miner.summary()
    73      if ifError(summary)
    74          EntitySetStateName ENTITY_ID, 'ERROR'
    75          return
    76      p = unmarshal summary.result
    77      attrs = {}
    78      attrs["ghs_av"] = p["GHS av"] 
    79      attrs["hardware_errors"] = p["Hardware Errors"] 
    80      EntitySetState ENTITY_ID,
    81          new_state: 'ENABLED'
    82          attribute_values: attrs
    83          storage_save: true
    84  
    85  checkDevs =->
    86      devs = Miner.devs()
    87      if ifError(devs)
    88          EntitySetStateName ENTITY_ID, 'ERROR'
    89          return
    90      p = unmarshal devs.result
    91      attrs = {}
    92      attrs["ghs_av"] = p["GHS av"] 
    93      attrs["hardware_errors"] = p["Hardware Errors"] 
    94      EntitySetState ENTITY_ID,
    95          new_state: 'ENABLED'
    96          attribute_values: attrs
    97          storage_save: true
    98  
    99  checkPools =->
   100      pools = Miner.pools()
   101      if ifError(pools)
   102          EntitySetStateName ENTITY_ID, 'ERROR'
   103          return
   104      p = unmarshal pools.result
   105      So(p.length, 'ShouldEqual', '4')
   106  
   107      So(p[0].POOL, 'ShouldEqual', '0')
   108      So(p[0].Accepted, 'ShouldEqual', '47169')
   109      So(p[0]["Stratum URL"], 'ShouldEqual', 'litecoinpool.org')
   110      So(p[0]["User"], 'ShouldEqual', 'user.4')
   111      So(p[0]["URL"], 'ShouldEqual', 'stratum+tcp://litecoinpool.org:3333')
   112  
   113      So(p[1].POOL, 'ShouldEqual', '1')
   114      So(p[1].Accepted, 'ShouldEqual', '0')
   115      So(p[1]["Stratum URL"], 'ShouldEqual', '')
   116      So(p[1]["User"], 'ShouldEqual', 'user.4')
   117      So(p[1]["URL"], 'ShouldEqual', 'stratum+tcp://us.litecoinpool.org:3333')
   118  
   119      So(p[2].POOL, 'ShouldEqual', '2')
   120      So(p[2].Accepted, 'ShouldEqual', '0')
   121      So(p[2]["Stratum URL"], 'ShouldEqual', '')
   122      So(p[2]["User"], 'ShouldEqual', 'user.4')
   123      So(p[2]["URL"], 'ShouldEqual', 'stratum+tcp://us2.litecoinpool.org:3333')
   124  
   125      So(p[3].POOL, 'ShouldEqual', '3')
   126      So(p[3].Accepted, 'ShouldEqual', '5502')
   127      So(p[3]["Stratum URL"], 'ShouldEqual', 'proto3.bytewall.net')
   128      So(p[3]["User"], 'ShouldEqual', '*')
   129      So(p[3]["URL"], 'ShouldEqual', '*')
   130  
   131  checkVer =(entityId)->
   132      ver = Miner.version()
   133      if ifError(ver)
   134          EntitySetStateName ENTITY_ID, 'ERROR'
   135          return
   136      p = unmarshal ver.result
   137      So(p.API, 'ShouldEqual', '3.1')
   138      So(p.Miner, 'ShouldEqual', '1.0.1.3')
   139      So(p.CompileTime, 'ShouldEqual', 'Fri 11 Jun 15:32:42 MSK 2021')
   140      So(p.Type, 'ShouldEqual', 'Antminer L3+ (024)')
   141  
   142  entityAction = (entityId, actionName)->
   143      switch actionName
   144          when 'CHECK' then checkStatus()
   145          when 'SUM' then checkSum()
   146          when 'DEVS' then checkDevs()
   147          when 'POOLS' then checkPools()
   148          when 'VER' then checkVer(entityId)
   149  `
   150  
   151  	const L3PlusStatsJson = `{"STATUS":[{"STATUS":"S","When":1633268527,"Code":70,"Msg":"CGMiner stats","Description":"cgminer 4.9.0 rwglr"}],"STATS":[{"CGMiner":"4.9.0 rwglr","Miner":"1.0.1.3","CompileTime":"Fri 11 Jun 15:32:42 MSK 2021","Type":"Antminer L3+ (024)"}{"STATS":0,"ID":"L30","Elapsed":373469,"Calls":0,"Wait":0.000000,"Max":0.000000,"Min":99999999.000000,"GHS 5s":"415.065","GHS av":411.84,"miner_count":3,"frequency":"425","fan_num":2,"frequency1":"425","frequency2":"422","frequency3":"423","frequency4":"419","fan1":3180,"fan2":3090,"temp_num":3,"temp1":0,"temp2":37,"temp3":37,"temp4":36,"temp2_1":0,"temp2_2":46,"temp2_3":44,"temp2_4":42,"temp31":0,"temp32":0,"temp33":0,"temp34":0,"temp4_1":0,"temp4_2":0,"temp4_3":0,"temp4_4":0,"temp_max":38,"Device Hardware%":0.0000,"no_matching_work":327,"chain_acn1":0,"chain_acn2":72,"chain_acn3":72,"chain_acn4":72,"chain_acs1":"","chain_acs2":" oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo","chain_acs3":" oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo","chain_acs4":" oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo","chain_hw1":0,"chain_hw2":26,"chain_hw3":43,"chain_hw4":258,"chain_rate1":"","chain_rate2":"138.50","chain_rate3":"138.85","chain_rate4":"137.72","chain_power1":0.00,"chain_power2":187.79,"chain_power3":188.24,"chain_power4":186.46,"chain_power":562.48,"voltage1":9.44,"voltage2":9.44,"voltage3":9.44,"voltage4":9.44}],"id":1}`
   152  	const L3PlusSummaryJson = `{"STATUS":[{"STATUS":"S","When":1633279121,"Code":11,"Msg":"Summary","Description":"cgminer 4.9.0 rwglr"}],"SUMMARY":[{"Elapsed":384063,"GHS 5s":"412.925","GHS av":411.85,"Found Blocks":0,"Getworks":37619,"Accepted":46423,"Rejected":530,"Hardware Errors":338,"Utility":7.25,"Discarded":226550,"Stale":21,"Get Failures":169,"Local Work":338070,"Remote Failures":4,"Network Blocks":2603,"Total MH":158174645.0000,"Work Utility":377447.21,"Difficulty Accepted":2397855744.00000000,"Difficulty Rejected":18202624.00000000,"Difficulty Stale":0.00000000,"Best Share":7510923133,"Device Hardware%":0.0000,"Device Rejected%":0.7534,"Pool Rejected%":0.7534,"Pool Stale%":0.0000,"Last getwork":1633279119}],"id":1}`
   153  	const L3PlusPoolsJson = `{"STATUS":[{"STATUS":"S","When":1633329118,"Code":7,"Msg":"4 Pool(s)","Description":"cgminer 4.9.0 rwglr"}],"POOLS":[{"POOL":0,"URL":"stratum+tcp://litecoinpool.org:3333","Status":"Alive","Priority":0,"Quota":1,"Long Poll":"N","Getworks":19515,"Accepted":47169,"Rejected":331,"Discarded":250226,"Stale":16,"Get Failures":2,"Remote Failures":0,"User":"user.4","Last Share Time":"0:00:06","Diff":"65.5K","Diff1 Shares":10494726,"Proxy Type":"","Proxy":"","Difficulty Accepted":2659909632.00000000,"Difficulty Rejected":18219008.00000000,"Difficulty Stale":0.00000000,"Last Share Difficulty":65536.00000000,"Has Stratum":true,"Stratum Active":true,"Stratum URL":"litecoinpool.org","Has GBT":false,"Best Share":7510923133,"Pool Rejected%":0.6803,"Pool Stale%":0.0000},{"POOL":1,"URL":"stratum+tcp://us.litecoinpool.org:3333","Status":"Alive","Priority":1,"Quota":1,"Long Poll":"N","Getworks":1,"Accepted":0,"Rejected":0,"Discarded":0,"Stale":0,"Get Failures":0,"Remote Failures":0,"User":"user.4","Last Share Time":"0","Diff":"","Diff1 Shares":0,"Proxy Type":"","Proxy":"","Difficulty Accepted":0.00000000,"Difficulty Rejected":0.00000000,"Difficulty Stale":0.00000000,"Last Share Difficulty":0.00000000,"Has Stratum":true,"Stratum Active":false,"Stratum URL":"","Has GBT":false,"Best Share":0,"Pool Rejected%":0.0000,"Pool Stale%":0.0000},{"POOL":2,"URL":"stratum+tcp://us2.litecoinpool.org:3333","Status":"Alive","Priority":2,"Quota":1,"Long Poll":"N","Getworks":1,"Accepted":0,"Rejected":0,"Discarded":0,"Stale":0,"Get Failures":0,"Remote Failures":0,"User":"user.4","Last Share Time":"0","Diff":"","Diff1 Shares":0,"Proxy Type":"","Proxy":"","Difficulty Accepted":0.00000000,"Difficulty Rejected":0.00000000,"Difficulty Stale":0.00000000,"Last Share Difficulty":0.00000000,"Has Stratum":true,"Stratum Active":false,"Stratum URL":"","Has GBT":false,"Best Share":0,"Pool Rejected%":0.0000,"Pool Stale%":0.0000},{"POOL":3,"URL":"*","Status":"Alive","Priority":999,"Quota":1,"Long Poll":"N","Getworks":22556,"Accepted":5502,"Rejected":288,"Discarded":5377,"Stale":5,"Get Failures":200,"Remote Failures":4,"User":"*","Last Share Time":"0:14:56","Diff":"131K","Diff1 Shares":225056,"Proxy Type":"","Proxy":"","Difficulty Accepted":54272000.00000000,"Difficulty Rejected":2957312.00000000,"Difficulty Stale":0.00000000,"Last Share Difficulty":131072.00000000,"Has Stratum":true,"Stratum Active":true,"Stratum URL":"proto3.bytewall.net","Has GBT":false,"Best Share":66992548,"Pool Rejected%":5.1675,"Pool Stale%":0.0000}],"id":1}`
   154  	const L3PlusVerJson = `{"STATUS":[{"STATUS":"S","When":1633332261,"Code":22,"Msg":"CGMiner versions","Description":"cgminer 4.9.0 rwglr"}],"VERSION":[{"CGMiner":"4.9.0 rwglr","API":"3.1","Miner":"1.0.1.3","CompileTime":"Fri 11 Jun 15:32:42 MSK 2021","Type":"Antminer L3+ (024)"}],"id":1}`
   155  
   156  	var getFixture = func(str string) []byte {
   157  		return append([]byte(str), byte(0x00))
   158  	}
   159  
   160  	Convey("cgminer", t, func(ctx C) {
   161  		_ = container.Invoke(func(adaptors *adaptors.Adaptors,
   162  			scriptService scripts.ScriptService,
   163  			supervisor supervisor.Supervisor,
   164  			eventBus bus.Bus) {
   165  
   166  			// register plugins
   167  			AddPlugin(adaptors, "cgminer")
   168  
   169  			serviceCh := WaitService(eventBus, time.Second*5, "Supervisor")
   170  			pluginsCh := WaitPlugins(eventBus, time.Second*5, "cgminer")
   171  			supervisor.Start(context.Background())
   172  			defer supervisor.Shutdown(context.Background())
   173  			So(<-serviceCh, ShouldBeTrue)
   174  			So(<-pluginsCh, ShouldBeTrue)
   175  
   176  			// bind convey
   177  			RegisterConvey(scriptService, ctx)
   178  
   179  			// add scripts
   180  			// ------------------------------------------------
   181  
   182  			plugScript, err := AddScript("cgminer script", cgminerSourceScript, adaptors, scriptService)
   183  			So(err, ShouldBeNil)
   184  
   185  			// add entity
   186  			// ------------------------------------------------
   187  			var port = GetPort()
   188  			var host = "127.0.0.1"
   189  
   190  			l3Ent := GetNewBitmineL3("device1")
   191  			l3Ent.Settings[cgminer.SettingHost].Value = host
   192  			l3Ent.Settings[cgminer.SettingPort].Value = port
   193  			l3Ent.Actions = []*m.EntityAction{
   194  				{
   195  					Name:        "ENABLE",
   196  					Description: "включить",
   197  					Script:      plugScript,
   198  				},
   199  				{
   200  					Name:        "DISABLE",
   201  					Description: "выключить",
   202  					Script:      plugScript,
   203  				},
   204  				{
   205  					Name:        "CHECK",
   206  					Description: "condition check",
   207  					Script:      plugScript,
   208  				},
   209  				{
   210  					Name:        "SUM",
   211  					Description: "summary",
   212  					Script:      plugScript,
   213  				},
   214  				{
   215  					Name:        "DEVS",
   216  					Description: "devs",
   217  					Script:      plugScript,
   218  				},
   219  				{
   220  					Name:        "POOLS",
   221  					Description: "pools",
   222  					Script:      plugScript,
   223  				},
   224  				{
   225  					Name:        "VER",
   226  					Description: "ver",
   227  					Script:      plugScript,
   228  				},
   229  			}
   230  			l3Ent.States = []*m.EntityState{
   231  				{
   232  					Name:        "ENABLED",
   233  					Description: "enabled state",
   234  				},
   235  				{
   236  					Name:        "DISABLED",
   237  					Description: "disabled state",
   238  				},
   239  				{
   240  					Name:        "ERROR",
   241  					Description: "error state",
   242  				},
   243  			}
   244  			l3Ent.Attributes = m.Attributes{
   245  				"heat": {
   246  					Name: "heat",
   247  					Type: common.AttributeBool,
   248  				},
   249  				"chain1_temp_chip": {
   250  					Name: "chain1_temp_chip",
   251  					Type: common.AttributeInt,
   252  				},
   253  				"chain2_temp_chip": {
   254  					Name: "chain2_temp_chip",
   255  					Type: common.AttributeInt,
   256  				},
   257  				"chain3_temp_chip": {
   258  					Name: "chain3_temp_chip",
   259  					Type: common.AttributeInt,
   260  				},
   261  				"chain4_temp_chip": {
   262  					Name: "chain4_temp_chip",
   263  					Type: common.AttributeInt,
   264  				},
   265  				"chain1_temp_pcb": {
   266  					Name: "chain1_temp_pcb",
   267  					Type: common.AttributeInt,
   268  				},
   269  				"chain2_temp_pcb": {
   270  					Name: "chain2_temp_pcb",
   271  					Type: common.AttributeInt,
   272  				},
   273  				"chain3_temp_pcb": {
   274  					Name: "chain3_temp_pcb",
   275  					Type: common.AttributeInt,
   276  				},
   277  				"chain4_temp_pcb": {
   278  					Name: "chain4_temp_pcb",
   279  					Type: common.AttributeInt,
   280  				},
   281  				"fan1": {
   282  					Name: "fan1",
   283  					Type: common.AttributeInt,
   284  				},
   285  				"fan2": {
   286  					Name: "fan2",
   287  					Type: common.AttributeInt,
   288  				},
   289  				"ghs_av": {
   290  					Name: "ghs_av",
   291  					Type: common.AttributeInt,
   292  				},
   293  				"hardware_errors": {
   294  					Name: "hardware_errors",
   295  					Type: common.AttributeInt,
   296  				},
   297  			}
   298  			err = adaptors.Entity.Add(context.Background(), l3Ent)
   299  			ctx.So(err, ShouldBeNil)
   300  			_, err = adaptors.EntityStorage.Add(context.Background(), &m.EntityStorage{
   301  				EntityId:   l3Ent.Id,
   302  				Attributes: l3Ent.Attributes.Serialize(),
   303  			})
   304  			So(err, ShouldBeNil)
   305  
   306  			eventBus.Publish("system/models/entities/"+l3Ent.Id.String(), events.EventCreatedEntityModel{
   307  				EntityId: l3Ent.Id,
   308  			})
   309  
   310  			time.Sleep(time.Second)
   311  
   312  			// ------------------------------------------------
   313  
   314  			t.Run("antminer L3+ stats", func(t *testing.T) {
   315  				Convey("stats", t, func(ctx C) {
   316  
   317  					ctx2, cancel := context.WithCancel(context.Background())
   318  					go func() { _ = MockTCPServer(ctx2, host, port, getFixture(L3PlusStatsJson)) }()
   319  					time.Sleep(time.Millisecond * 500)
   320  					supervisor.CallAction(l3Ent.Id, "CHECK", nil)
   321  					time.Sleep(time.Second)
   322  					cancel()
   323  
   324  					lastState, err := adaptors.EntityStorage.GetLastByEntityId(context.Background(), l3Ent.Id)
   325  					ctx.So(err, ShouldBeNil)
   326  
   327  					debug.Println(lastState)
   328  
   329  					ctx.So(lastState.Attributes["chain1_temp_chip"], ShouldEqual, 0)
   330  					ctx.So(lastState.Attributes["chain1_temp_pcb"], ShouldEqual, 0)
   331  					ctx.So(lastState.Attributes["chain2_temp_chip"], ShouldEqual, 46)
   332  					ctx.So(lastState.Attributes["chain2_temp_pcb"], ShouldEqual, 37)
   333  					ctx.So(lastState.Attributes["chain3_temp_chip"], ShouldEqual, 44)
   334  					ctx.So(lastState.Attributes["chain3_temp_pcb"], ShouldEqual, 37)
   335  					ctx.So(lastState.Attributes["chain4_temp_chip"], ShouldEqual, 42)
   336  					ctx.So(lastState.Attributes["chain4_temp_pcb"], ShouldEqual, 36)
   337  					ctx.So(lastState.Attributes["fan1"], ShouldEqual, 3180)
   338  					ctx.So(lastState.Attributes["fan2"], ShouldEqual, 3090)
   339  				})
   340  			})
   341  
   342  			t.Run("antminer L3+ summary", func(t *testing.T) {
   343  				Convey("summary", t, func(ctx C) {
   344  
   345  					ctx2, cancel := context.WithCancel(context.Background())
   346  					go func() { _ = MockTCPServer(ctx2, host, port, getFixture(L3PlusSummaryJson)) }()
   347  					time.Sleep(time.Millisecond * 500)
   348  					supervisor.CallAction(l3Ent.Id, "SUM", nil)
   349  					time.Sleep(time.Second)
   350  					cancel()
   351  
   352  					lastState, err := adaptors.EntityStorage.GetLastByEntityId(context.Background(), l3Ent.Id)
   353  					ctx.So(err, ShouldBeNil)
   354  
   355  					ctx.So(lastState.Attributes["ghs_av"], ShouldEqual, 411.85)
   356  					ctx.So(lastState.Attributes["hardware_errors"], ShouldEqual, 338)
   357  				})
   358  			})
   359  
   360  			t.Run("antminer L3+ pools", func(t *testing.T) {
   361  				Convey("pools", t, func(ctx C) {
   362  
   363  					ctx2, cancel := context.WithCancel(context.Background())
   364  					go func() { _ = MockTCPServer(ctx2, host, port, getFixture(L3PlusPoolsJson)) }()
   365  					time.Sleep(time.Millisecond * 500)
   366  					supervisor.CallAction(l3Ent.Id, "POOLS", nil)
   367  					time.Sleep(time.Second)
   368  					cancel()
   369  				})
   370  			})
   371  
   372  			t.Run("antminer L3+ version", func(t *testing.T) {
   373  				Convey("version", t, func(ctx C) {
   374  
   375  					ctx2, cancel := context.WithCancel(context.Background())
   376  					go func() { _ = MockTCPServer(ctx2, host, port, getFixture(L3PlusVerJson)) }()
   377  					time.Sleep(time.Millisecond * 500)
   378  					supervisor.CallAction(l3Ent.Id, "VER", nil)
   379  					time.Sleep(time.Second)
   380  					cancel()
   381  				})
   382  			})
   383  
   384  		})
   385  	})
   386  }