From ad2dcc46e44e54e9beeed8beab323e62a07af9f6 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 8 Mar 2022 12:50:47 +0100 Subject: [PATCH 1/5] Added pool hashrate chart --- backend/src/index.ts | 1 + .../src/repositories/HashratesRepository.ts | 35 +++++ backend/src/routes.ts | 16 +++ .../app/components/pool/pool.component.html | 95 ++++++------- .../app/components/pool/pool.component.scss | 3 +- .../src/app/components/pool/pool.component.ts | 129 +++++++++++++++++- frontend/src/app/services/api.service.ts | 4 + 7 files changed, 228 insertions(+), 55 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 4ede865a6..fb358b1f3 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -299,6 +299,7 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/2y', routes.$getPools.bind(routes, '2y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3y', routes.$getPools.bind(routes, '3y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/all', routes.$getPools.bind(routes, 'all')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/hashrate/:interval', routes.$getPoolHistoricalHashrate) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks', routes.$getPoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks/:height', routes.$getPoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool) diff --git a/backend/src/repositories/HashratesRepository.ts b/backend/src/repositories/HashratesRepository.ts index 3523004d5..a217dcc16 100644 --- a/backend/src/repositories/HashratesRepository.ts +++ b/backend/src/repositories/HashratesRepository.ts @@ -116,6 +116,41 @@ class HashratesRepository { } } + /** + * Returns a pool hashrate history + */ + public async $getPoolWeeklyHashrate(interval: string | null, poolId: number): Promise { + interval = Common.getSqlInterval(interval); + + const connection = await DB.pool.getConnection(); + + let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate, share, pools.name as poolName + FROM hashrates + JOIN pools on pools.id = pool_id`; + + if (interval) { + query += ` WHERE hashrate_timestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() + AND hashrates.type = 'weekly' + AND pool_id = ${poolId}`; + } else { + query += ` WHERE hashrates.type = 'weekly' + AND pool_id = ${poolId}`; + } + + query += ` ORDER by hashrate_timestamp`; + + try { + const [rows]: any[] = await connection.query(query); + connection.release(); + + return rows; + } catch (e) { + connection.release(); + logger.err('$getPoolWeeklyHashrate() error' + (e instanceof Error ? e.message : e)); + throw e; + } + } + public async $setLatestRunTimestamp(key: string, val: any = null) { const connection = await DB.pool.getConnection(); const query = `UPDATE state SET number = ? WHERE name = ?`; diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 6b1a365b6..69dde1a59 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -603,6 +603,22 @@ class Routes { } } + public async $getPoolHistoricalHashrate(req: Request, res: Response) { + try { + const hashrates = await HashratesRepository.$getPoolWeeklyHashrate(req.params.interval ?? null, parseInt(req.params.poolId, 10)); + const oldestIndexedBlockTimestamp = await BlocksRepository.$oldestBlockTimestamp(); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); + res.json({ + oldestIndexedBlockTimestamp: oldestIndexedBlockTimestamp, + hashrates: hashrates, + }); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + public async $getHistoricalHashrate(req: Request, res: Response) { try { const hashrates = await HashratesRepository.$getNetworkDailyHashrate(req.params.interval ?? null); diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 43bc647e8..8478aa566 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -1,47 +1,34 @@
-

- - {{ poolStats.pool.name }} -

- -
-
-
-
- - - - - - - - - - -
-
+
+

+ + {{ poolStats.pool.name }} +

+
+
+
+
+ + + + + +
+
+
@@ -54,10 +41,13 @@ Addresses - ~ + + ~ + Coinbase Tags @@ -84,7 +74,13 @@
- +
+
+
+
+ +
@@ -97,12 +93,17 @@ - - + + diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index 271696a39..65f16b6b8 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -18,9 +18,8 @@ display: flex; flex-direction: column; @media (min-width: 830px) { - margin-left: 2%; flex-direction: row; - float: left; + float: right; margin-top: 0px; } .btn-sm { diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 9d094dce0..7cc909abd 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -1,11 +1,14 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; +import { EChartsOption, graphic } from 'echarts'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; -import { distinctUntilChanged, map, startWith, switchMap, tap } from 'rxjs/operators'; +import { distinctUntilChanged, map, startWith, switchMap, tap, toArray } from 'rxjs/operators'; import { BlockExtended, PoolStat } from 'src/app/interfaces/node-api.interface'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; +import { selectPowerOfTen } from 'src/app/bitcoin.utils'; +import { formatNumber } from '@angular/common'; @Component({ selector: 'app-pool', @@ -14,34 +17,57 @@ import { StateService } from 'src/app/services/state.service'; changeDetection: ChangeDetectionStrategy.OnPush }) export class PoolComponent implements OnInit { + @Input() right: number | string = 45; + @Input() left: number | string = 75; + poolStats$: Observable; blocks$: Observable; + isLoading = true; + + radioGroupForm: FormGroup; + + chartOptions: EChartsOption = {}; + chartInitOptions = { + renderer: 'svg', + width: 'auto', + height: 'auto', + }; fromHeight: number = -1; fromHeightSubject: BehaviorSubject = new BehaviorSubject(this.fromHeight); blocks: BlockExtended[] = []; poolId: number = undefined; - radioGroupForm: FormGroup; constructor( + @Inject(LOCALE_ID) public locale: string, private apiService: ApiService, private route: ActivatedRoute, public stateService: StateService, private formBuilder: FormBuilder, ) { - this.radioGroupForm = this.formBuilder.group({ dateSpan: '1w' }); - this.radioGroupForm.controls.dateSpan.setValue('1w'); + this.radioGroupForm = this.formBuilder.group({ dateSpan: 'all' }); + this.radioGroupForm.controls.dateSpan.setValue('all'); } ngOnInit(): void { this.poolStats$ = combineLatest([ this.route.params.pipe(map((params) => params.poolId)), - this.radioGroupForm.get('dateSpan').valueChanges.pipe(startWith('1w')), + this.radioGroupForm.get('dateSpan').valueChanges.pipe(startWith('all')), ]) .pipe( switchMap((params: any) => { this.poolId = params[0]; + return this.apiService.getPoolHashrate$(this.poolId, params[1] ?? 'all') + .pipe( + switchMap((data) => { + this.prepareChartOptions(data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate])); + return params; + }), + toArray(), + ) + }), + switchMap((params: any) => { if (this.blocks.length === 0) { this.fromHeightSubject.next(undefined); } @@ -55,6 +81,7 @@ export class PoolComponent implements OnInit { poolStats.pool.regexes = regexes.slice(0, -3); poolStats.pool.addresses = poolStats.pool.addresses; + this.isLoading = false; return Object.assign({ logo: `./resources/mining-pools/` + poolStats.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg' }, poolStats); @@ -74,6 +101,96 @@ export class PoolComponent implements OnInit { ) } + prepareChartOptions(data) { + this.chartOptions = { + animation: false, + color: [ + new graphic.LinearGradient(0, 0, 0, 0.65, [ + { offset: 0, color: '#F4511E' }, + { offset: 0.25, color: '#FB8C00' }, + { offset: 0.5, color: '#FFB300' }, + { offset: 0.75, color: '#FDD835' }, + { offset: 1, color: '#7CB342' } + ]), + '#D81B60', + ], + grid: { + right: this.right, + left: this.left, + bottom: 60, + }, + tooltip: { + show: !this.isMobile(), + trigger: 'axis', + axisPointer: { + type: 'line' + }, + backgroundColor: 'rgba(17, 19, 31, 1)', + borderRadius: 4, + shadowColor: 'rgba(0, 0, 0, 0.5)', + textStyle: { + color: '#b1b1b1', + align: 'left', + }, + borderColor: '#000', + formatter: function (data) { + let hashratePowerOfTen: any = selectPowerOfTen(1); + let hashrate = data[0].data[1]; + + if (this.isMobile()) { + hashratePowerOfTen = selectPowerOfTen(data[0].data[1]); + hashrate = Math.round(data[0].data[1] / hashratePowerOfTen.divider); + } + + return ` + ${data[0].axisValueLabel}
+ ${data[0].marker} ${data[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s
+ `; + }.bind(this) + }, + xAxis: { + type: 'time', + splitNumber: (this.isMobile()) ? 5 : 10, + }, + yAxis: [ + { + min: function (value) { + return value.min * 0.9; + }, + type: 'value', + name: 'Hashrate', + axisLabel: { + color: 'rgb(110, 112, 121)', + formatter: (val) => { + const selectedPowerOfTen: any = selectPowerOfTen(val); + const newVal = Math.round(val / selectedPowerOfTen.divider); + return `${newVal} ${selectedPowerOfTen.unit}H/s` + } + }, + splitLine: { + show: false, + } + }, + ], + series: [ + { + name: 'Hashrate', + showSymbol: false, + symbol: 'none', + data: data, + type: 'line', + lineStyle: { + width: 2, + }, + }, + ], + }; + } + + isMobile() { + return (window.innerWidth <= 767.98); + } + loadMore() { this.fromHeightSubject.next(this.blocks[this.blocks.length - 1]?.height); } diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 5548780b1..fcac428a9 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -143,6 +143,10 @@ export class ApiService { ); } + getPoolHashrate$(poolId: number, interval: string | undefined): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/hashrate/${interval}`); + } + getPoolBlocks$(poolId: number, fromHeight: number): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/blocks` + From 2b5d972e8d94d7ce9dcc427028d410c59a720932 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 8 Mar 2022 13:54:04 +0100 Subject: [PATCH 2/5] Only show relevant hashrate in the pool page --- backend/src/api/mining.ts | 6 +-- backend/src/index.ts | 2 +- backend/src/repositories/BlocksRepository.ts | 2 +- .../src/repositories/HashratesRepository.ts | 45 ++++++++++++------- backend/src/routes.ts | 4 +- .../app/components/pool/pool.component.html | 35 +++------------ .../src/app/components/pool/pool.component.ts | 28 ++++-------- frontend/src/app/services/api.service.ts | 11 ++--- 8 files changed, 53 insertions(+), 80 deletions(-) diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index 4423e5f16..0724a6612 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -58,14 +58,14 @@ class Mining { /** * Get all mining pool stats for a pool */ - public async $getPoolStat(interval: string | null, poolId: number): Promise { + public async $getPoolStat(poolId: number): Promise { const pool = await PoolsRepository.$getPool(poolId); if (!pool) { throw new Error(`This mining pool does not exist`); } - const blockCount: number = await BlocksRepository.$blockCount(poolId, interval); - const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$getEmptyBlocks(poolId, interval); + const blockCount: number = await BlocksRepository.$blockCount(poolId); + const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$getEmptyBlocks(poolId); return { pool: pool, diff --git a/backend/src/index.ts b/backend/src/index.ts index fb358b1f3..c2de54521 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -299,7 +299,7 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/2y', routes.$getPools.bind(routes, '2y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3y', routes.$getPools.bind(routes, '3y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/all', routes.$getPools.bind(routes, 'all')) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/hashrate/:interval', routes.$getPoolHistoricalHashrate) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/hashrate', routes.$getPoolHistoricalHashrate) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks', routes.$getPoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks/:height', routes.$getPoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool) diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 844f62bad..33f7a2d97 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -134,7 +134,7 @@ class BlocksRepository { /** * Get blocks count for a period */ - public async $blockCount(poolId: number | null, interval: string | null): Promise { + public async $blockCount(poolId: number | null, interval: string | null = null): Promise { interval = Common.getSqlInterval(interval); const params: any[] = []; diff --git a/backend/src/repositories/HashratesRepository.ts b/backend/src/repositories/HashratesRepository.ts index a217dcc16..4371b9e0a 100644 --- a/backend/src/repositories/HashratesRepository.ts +++ b/backend/src/repositories/HashratesRepository.ts @@ -119,28 +119,39 @@ class HashratesRepository { /** * Returns a pool hashrate history */ - public async $getPoolWeeklyHashrate(interval: string | null, poolId: number): Promise { - interval = Common.getSqlInterval(interval); - + public async $getPoolWeeklyHashrate(poolId: number): Promise { const connection = await DB.pool.getConnection(); - let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate, share, pools.name as poolName - FROM hashrates - JOIN pools on pools.id = pool_id`; - - if (interval) { - query += ` WHERE hashrate_timestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() - AND hashrates.type = 'weekly' - AND pool_id = ${poolId}`; - } else { - query += ` WHERE hashrates.type = 'weekly' - AND pool_id = ${poolId}`; - } - - query += ` ORDER by hashrate_timestamp`; + // Find hashrate boundaries + let query = `SELECT MIN(hashrate_timestamp) as firstTimestamp, MAX(hashrate_timestamp) as lastTimestamp + FROM hashrates + JOIN pools on pools.id = pool_id + WHERE hashrates.type = 'weekly' AND pool_id = ${poolId} AND avg_hashrate != 0 + ORDER by hashrate_timestamp LIMIT 1` + let boundaries = { + firstTimestamp: '1970-01-01', + lastTimestamp: '9999-01-01' + }; try { const [rows]: any[] = await connection.query(query); + boundaries = rows[0]; + connection.release(); + } catch (e) { + connection.release(); + logger.err('$getPoolWeeklyHashrate() error' + (e instanceof Error ? e.message : e)); + } + + // Get hashrates entries between boundaries + query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate, share, pools.name as poolName + FROM hashrates + JOIN pools on pools.id = pool_id + WHERE hashrates.type = 'weekly' AND hashrate_timestamp BETWEEN ? AND ? + AND pool_id = ${poolId} + ORDER by hashrate_timestamp`; + + try { + const [rows]: any[] = await connection.query(query, [boundaries.firstTimestamp, boundaries.lastTimestamp]); connection.release(); return rows; diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 69dde1a59..8ccc9c2e1 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -538,7 +538,7 @@ class Routes { public async $getPool(req: Request, res: Response) { try { - const stats = await mining.$getPoolStat(req.params.interval ?? null, parseInt(req.params.poolId, 10)); + const stats = await mining.$getPoolStat(parseInt(req.params.poolId, 10)); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); @@ -605,7 +605,7 @@ class Routes { public async $getPoolHistoricalHashrate(req: Request, res: Response) { try { - const hashrates = await HashratesRepository.$getPoolWeeklyHashrate(req.params.interval ?? null, parseInt(req.params.poolId, 10)); + const hashrates = await HashratesRepository.$getPoolWeeklyHashrate(parseInt(req.params.poolId, 10)); const oldestIndexedBlockTimestamp = await BlocksRepository.$oldestBlockTimestamp(); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 8478aa566..4e6d78d2b 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -1,36 +1,11 @@
-
-

- - {{ poolStats.pool.name }} -

-
-
-
-
- - - - - -
- -
-
-
+

+ + {{ poolStats.pool.name }} +

diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 7cc909abd..282febf2f 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -1,9 +1,8 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { EChartsOption, graphic } from 'echarts'; -import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; -import { distinctUntilChanged, map, startWith, switchMap, tap, toArray } from 'rxjs/operators'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { distinctUntilChanged, map, switchMap, tap, toArray } from 'rxjs/operators'; import { BlockExtended, PoolStat } from 'src/app/interfaces/node-api.interface'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; @@ -24,8 +23,6 @@ export class PoolComponent implements OnInit { blocks$: Observable; isLoading = true; - radioGroupForm: FormGroup; - chartOptions: EChartsOption = {}; chartInitOptions = { renderer: 'svg', @@ -44,34 +41,27 @@ export class PoolComponent implements OnInit { private apiService: ApiService, private route: ActivatedRoute, public stateService: StateService, - private formBuilder: FormBuilder, ) { - this.radioGroupForm = this.formBuilder.group({ dateSpan: 'all' }); - this.radioGroupForm.controls.dateSpan.setValue('all'); } ngOnInit(): void { - this.poolStats$ = combineLatest([ - this.route.params.pipe(map((params) => params.poolId)), - this.radioGroupForm.get('dateSpan').valueChanges.pipe(startWith('all')), - ]) + this.poolStats$ = this.route.params.pipe(map((params) => params.poolId)) .pipe( - switchMap((params: any) => { - this.poolId = params[0]; - return this.apiService.getPoolHashrate$(this.poolId, params[1] ?? 'all') + switchMap((poolId: any) => { + this.poolId = poolId; + return this.apiService.getPoolHashrate$(this.poolId) .pipe( switchMap((data) => { this.prepareChartOptions(data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate])); - return params; + return poolId; }), - toArray(), ) }), - switchMap((params: any) => { + switchMap(() => { if (this.blocks.length === 0) { this.fromHeightSubject.next(undefined); } - return this.apiService.getPoolStats$(this.poolId, params[1] ?? '1w'); + return this.apiService.getPoolStats$(this.poolId); }), map((poolStats) => { let regexes = '"'; diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index fcac428a9..858da3273 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -136,15 +136,12 @@ export class ApiService { ); } - getPoolStats$(poolId: number, interval: string | undefined): Observable { - return this.httpClient.get( - this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}` + - (interval !== undefined ? `/${interval}` : '') - ); + getPoolStats$(poolId: number): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}`); } - getPoolHashrate$(poolId: number, interval: string | undefined): Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/hashrate/${interval}`); + getPoolHashrate$(poolId: number): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/hashrate`); } getPoolBlocks$(poolId: number, fromHeight: number): Observable { From f23f7f1cfa42d2e0a54ab7feb0d7edcca7e4cca0 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 8 Mar 2022 16:55:49 +0100 Subject: [PATCH 3/5] Cleanup empty block in api response - Update cache warmer --- backend/src/api/mining.ts | 16 ++++++---------- backend/src/repositories/BlocksRepository.ts | 15 ++++++--------- backend/src/repositories/HashratesRepository.ts | 13 ++++++++----- .../src/app/components/pool/pool.component.html | 2 +- production/nginx-cache-warmer | 8 ++++++++ 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index 0724a6612..2e094229b 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -1,5 +1,5 @@ import { PoolInfo, PoolStats } from '../mempool.interfaces'; -import BlocksRepository, { EmptyBlocks } from '../repositories/BlocksRepository'; +import BlocksRepository from '../repositories/BlocksRepository'; import PoolsRepository from '../repositories/PoolsRepository'; import HashratesRepository from '../repositories/HashratesRepository'; import bitcoinClient from './bitcoin/bitcoin-client'; @@ -20,25 +20,21 @@ class Mining { const poolsStatistics = {}; const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(interval); - const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$getEmptyBlocks(null, interval); + const emptyBlocks: any[] = await BlocksRepository.$countEmptyBlocks(null, interval); const poolsStats: PoolStats[] = []; let rank = 1; poolsInfo.forEach((poolInfo: PoolInfo) => { + const emptyBlocksCount = emptyBlocks.filter((emptyCount) => emptyCount.poolId === poolInfo.poolId); const poolStat: PoolStats = { poolId: poolInfo.poolId, // mysql row id name: poolInfo.name, link: poolInfo.link, blockCount: poolInfo.blockCount, rank: rank++, - emptyBlocks: 0 + emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0 }; - for (let i = 0; i < emptyBlocks.length; ++i) { - if (emptyBlocks[i].poolId === poolInfo.poolId) { - poolStat.emptyBlocks++; - } - } poolsStats.push(poolStat); }); @@ -65,12 +61,12 @@ class Mining { } const blockCount: number = await BlocksRepository.$blockCount(poolId); - const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$getEmptyBlocks(poolId); + const emptyBlocksCount = await BlocksRepository.$countEmptyBlocks(poolId); return { pool: pool, blockCount: blockCount, - emptyBlocks: emptyBlocks, + emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0, }; } diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 33f7a2d97..2364a8485 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -3,11 +3,6 @@ import { DB } from '../database'; import logger from '../logger'; import { Common } from '../api/common'; -export interface EmptyBlocks { - emptyBlocks: number; - poolId: number; -} - class BlocksRepository { /** * Save indexed block data in the database @@ -100,12 +95,13 @@ class BlocksRepository { /** * Get empty blocks for one or all pools */ - public async $getEmptyBlocks(poolId: number | null, interval: string | null = null): Promise { + public async $countEmptyBlocks(poolId: number | null, interval: string | null = null): Promise { interval = Common.getSqlInterval(interval); const params: any[] = []; - let query = `SELECT height, hash, tx_count, size, pool_id, weight, UNIX_TIMESTAMP(blockTimestamp) as timestamp + let query = `SELECT count(height) as count, pools.id as poolId FROM blocks + JOIN pools on pools.id = blocks.pool_id WHERE tx_count = 1`; if (poolId) { @@ -117,13 +113,14 @@ class BlocksRepository { query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; } - // logger.debug(query); + query += ` GROUP by pools.id`; + const connection = await DB.pool.getConnection(); try { const [rows] = await connection.query(query, params); connection.release(); - return rows; + return rows; } catch (e) { connection.release(); logger.err('$getEmptyBlocks() error' + (e instanceof Error ? e.message : e)); diff --git a/backend/src/repositories/HashratesRepository.ts b/backend/src/repositories/HashratesRepository.ts index 4371b9e0a..749d3cb57 100644 --- a/backend/src/repositories/HashratesRepository.ts +++ b/backend/src/repositories/HashratesRepository.ts @@ -126,15 +126,15 @@ class HashratesRepository { let query = `SELECT MIN(hashrate_timestamp) as firstTimestamp, MAX(hashrate_timestamp) as lastTimestamp FROM hashrates JOIN pools on pools.id = pool_id - WHERE hashrates.type = 'weekly' AND pool_id = ${poolId} AND avg_hashrate != 0 - ORDER by hashrate_timestamp LIMIT 1` + WHERE hashrates.type = 'weekly' AND pool_id = ? AND avg_hashrate != 0 + ORDER by hashrate_timestamp LIMIT 1`; let boundaries = { firstTimestamp: '1970-01-01', lastTimestamp: '9999-01-01' }; try { - const [rows]: any[] = await connection.query(query); + const [rows]: any[] = await connection.query(query, [poolId]); boundaries = rows[0]; connection.release(); } catch (e) { @@ -147,11 +147,11 @@ class HashratesRepository { FROM hashrates JOIN pools on pools.id = pool_id WHERE hashrates.type = 'weekly' AND hashrate_timestamp BETWEEN ? AND ? - AND pool_id = ${poolId} + AND pool_id = ? ORDER by hashrate_timestamp`; try { - const [rows]: any[] = await connection.query(query, [boundaries.firstTimestamp, boundaries.lastTimestamp]); + const [rows]: any[] = await connection.query(query, [boundaries.firstTimestamp, boundaries.lastTimestamp, poolId]); connection.release(); return rows; @@ -182,6 +182,9 @@ class HashratesRepository { const [rows] = await connection.query(query, [key]); connection.release(); + if (rows.length === 0) { + return 0; + } return rows[0]['number']; } catch (e) { connection.release(); diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 4e6d78d2b..66b3db5e3 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -40,7 +40,7 @@
- +
Height Timestamp
{{ block.height }} ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + + + + {{ block.tx_count | number }}
-
+
Empty Blocks{{ poolStats.emptyBlocks.length }}{{ poolStats.emptyBlocks }}
diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index 2eab09dd1..38db73215 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -38,5 +38,13 @@ do for url in / \ curl -s "https://${hostname}${url}" >/dev/null done + counter=1 + while [ $counter -le 134 ] + do + curl -s "https://${hostname}/api/v1/mining/pool/${counter}/hashrate" >/dev/null + curl -s "https://${hostname}/api/v1/mining/pool/${counter}" >/dev/null + ((counter++)) + done + sleep 10 done From 71d500d750bc380a4a9ba296473f99f8b4fccf20 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 8 Mar 2022 18:58:19 +0100 Subject: [PATCH 4/5] Fix pool detail page layout - add loading skeleton --- .../app/components/pool/pool.component.html | 67 ++++++++++++++++--- .../app/components/pool/pool.component.scss | 11 +++ .../src/app/components/pool/pool.component.ts | 9 +++ 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 66b3db5e3..7be7c220c 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -1,7 +1,7 @@
-
-

+
+

{{ poolStats.pool.name }} @@ -54,27 +54,27 @@

- - - + + - + - - +
Height Timestamp MinedRewardTransactionsRewardTransactions Size
{{ block.height }} ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + {{ block.tx_count | number }}{{ block.tx_count | number }}
-

\ No newline at end of file +
+ + +
+

+ +
+

+ +
+
+
+ + + + + + + + + + + + + + +
Addresses +
+
+
+
~
Coinbase Tags
+
+
+ + + + + + + + + + + +
Mined Blocks
Empty Blocks
+
+
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index 65f16b6b8..b7c496995 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -37,4 +37,15 @@ div.scrollable { padding: 0; overflow: auto; max-height: 100px; +} + +.skeleton-loader { + width: 100%; + max-width: 90px; + margin: 15px auto 3px; +} + +.table { + margin: 0px auto; + max-width: 900px; } \ No newline at end of file diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 282febf2f..0e0e116dd 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -13,6 +13,14 @@ import { formatNumber } from '@angular/common'; selector: 'app-pool', templateUrl: './pool.component.html', styleUrls: ['./pool.component.scss'], + styles: [` + .loadingGraphs { + position: absolute; + top: 50%; + left: calc(50% - 15px); + z-index: 100; + } + `], changeDetection: ChangeDetectionStrategy.OnPush }) export class PoolComponent implements OnInit { @@ -48,6 +56,7 @@ export class PoolComponent implements OnInit { this.poolStats$ = this.route.params.pipe(map((params) => params.poolId)) .pipe( switchMap((poolId: any) => { + this.isLoading = true; this.poolId = poolId; return this.apiService.getPoolHashrate$(this.poolId) .pipe( From 171246f4efda173e25f7006bf9a5ceb25303cd9b Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 8 Mar 2022 20:29:09 +0100 Subject: [PATCH 5/5] Fix skeleton layout jumping in pool detail page --- frontend/src/app/components/pool/pool.component.html | 4 ++-- frontend/src/app/components/pool/pool.component.scss | 1 - frontend/src/app/components/pool/pool.component.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 7be7c220c..3c2b78a34 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -2,7 +2,7 @@

- {{ poolStats.pool.name }}

@@ -91,7 +91,7 @@

- +

diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index b7c496995..2dfd85881 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -42,7 +42,6 @@ div.scrollable { .skeleton-loader { width: 100%; max-width: 90px; - margin: 15px auto 3px; } .table { diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 0e0e116dd..89d398ca3 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -61,6 +61,7 @@ export class PoolComponent implements OnInit { return this.apiService.getPoolHashrate$(this.poolId) .pipe( switchMap((data) => { + this.isLoading = false; this.prepareChartOptions(data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate])); return poolId; }), @@ -80,7 +81,6 @@ export class PoolComponent implements OnInit { poolStats.pool.regexes = regexes.slice(0, -3); poolStats.pool.addresses = poolStats.pool.addresses; - this.isLoading = false; return Object.assign({ logo: `./resources/mining-pools/` + poolStats.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg' }, poolStats);