From acd658a0e7de2f2d6dfbb1a162965397f37624a2 Mon Sep 17 00:00:00 2001 From: Simon Lindh Date: Mon, 17 Feb 2020 00:26:57 +0700 Subject: [PATCH] Optimize statistics. --- backend/src/api/statistics.ts | 98 ++++++++++++++++--- backend/src/index.ts | 1 + backend/src/interfaces.ts | 11 +++ backend/src/routes.ts | 5 + .../statistics/statistics.component.html | 2 +- .../statistics/statistics.component.ts | 28 +++--- .../television/television.component.ts | 20 ++-- .../src/app/interfaces/node-api.interface.ts | 44 +-------- frontend/src/app/services/api.service.ts | 30 +++--- frontend/src/app/services/state.service.ts | 4 +- frontend/src/styles.scss | 4 + 11 files changed, 148 insertions(+), 99 deletions(-) diff --git a/backend/src/api/statistics.ts b/backend/src/api/statistics.ts index 2d4fc8e15..9550d4935 100644 --- a/backend/src/api/statistics.ts +++ b/backend/src/api/statistics.ts @@ -1,7 +1,7 @@ import memPool from './mempool'; import { DB } from '../database'; -import { Statistic, SimpleTransaction } from '../interfaces'; +import { Statistic, SimpleTransaction, OptimizedStatistic } from '../interfaces'; class Statistics { protected intervalTimer: NodeJS.Timer | undefined; @@ -278,95 +278,163 @@ class Statistics { AVG(vsize_2000) AS vsize_2000 FROM statistics GROUP BY UNIX_TIMESTAMP(added) DIV ${groupBy} ORDER BY id DESC LIMIT ${days}`; } - public async $get(id: number): Promise { + public async $get(id: number): Promise { try { const connection = await DB.pool.getConnection(); const query = `SELECT * FROM statistics WHERE id = ?`; const [rows] = await connection.query(query, [id]); connection.release(); - return rows[0]; + if (rows[0]) { + return this.mapStatisticToOptimizedStatistic([rows[0]])[0]; + } } catch (e) { console.log('$list2H() error', e); } } - public async $list2H(): Promise { + public async $list2H(): Promise { try { const connection = await DB.pool.getConnection(); const query = `SELECT * FROM statistics ORDER BY id DESC LIMIT 120`; const [rows] = await connection.query(query); connection.release(); - return rows; + return this.mapStatisticToOptimizedStatistic(rows); } catch (e) { console.log('$list2H() error', e); return []; } } - public async $list24H(): Promise { + public async $list24H(): Promise { try { const connection = await DB.pool.getConnection(); const query = this.getQueryForDays(120, 720); const [rows] = await connection.query(query); connection.release(); - return rows; + return this.mapStatisticToOptimizedStatistic(rows); } catch (e) { return []; } } - public async $list1W(): Promise { + public async $list1W(): Promise { try { const connection = await DB.pool.getConnection(); const query = this.getQueryForDays(120, 5040); const [rows] = await connection.query(query); connection.release(); - return rows; + return this.mapStatisticToOptimizedStatistic(rows); } catch (e) { console.log('$list1W() error', e); return []; } } - public async $list1M(): Promise { + public async $list1M(): Promise { try { const connection = await DB.pool.getConnection(); const query = this.getQueryForDays(120, 20160); const [rows] = await connection.query(query); connection.release(); - return rows; + return this.mapStatisticToOptimizedStatistic(rows); } catch (e) { console.log('$list1M() error', e); return []; } } - public async $list3M(): Promise { + public async $list3M(): Promise { try { const connection = await DB.pool.getConnection(); const query = this.getQueryForDays(120, 60480); const [rows] = await connection.query(query); connection.release(); - return rows; + return this.mapStatisticToOptimizedStatistic(rows); } catch (e) { console.log('$list3M() error', e); return []; } } - public async $list6M(): Promise { + public async $list6M(): Promise { try { const connection = await DB.pool.getConnection(); const query = this.getQueryForDays(120, 120960); const [rows] = await connection.query(query); connection.release(); - return rows; + return this.mapStatisticToOptimizedStatistic(rows); } catch (e) { console.log('$list6M() error', e); return []; } } + public async $list1Y(): Promise { + try { + const connection = await DB.pool.getConnection(); + const query = this.getQueryForDays(120, 241920); + const [rows] = await connection.query(query); + connection.release(); + return this.mapStatisticToOptimizedStatistic(rows); + } catch (e) { + console.log('$list6M() error', e); + return []; + } + } + private mapStatisticToOptimizedStatistic(statistic: Statistic[]): OptimizedStatistic[] { + return statistic.map((s) => { + return { + id: s.id || 0, + added: s.added, + unconfirmed_transactions: s.unconfirmed_transactions, + tx_per_second: s.tx_per_second, + vbytes_per_second: s.vbytes_per_second, + mempool_byte_weight: s.mempool_byte_weight, + total_fee: s.total_fee, + vsizes: [ + s.vsize_1, + s.vsize_2, + s.vsize_3, + s.vsize_4, + s.vsize_5, + s.vsize_6, + s.vsize_8, + s.vsize_10, + s.vsize_12, + s.vsize_15, + s.vsize_20, + s.vsize_30, + s.vsize_40, + s.vsize_50, + s.vsize_60, + s.vsize_70, + s.vsize_80, + s.vsize_90, + s.vsize_100, + s.vsize_125, + s.vsize_150, + s.vsize_175, + s.vsize_200, + s.vsize_250, + s.vsize_300, + s.vsize_350, + s.vsize_400, + s.vsize_500, + s.vsize_600, + s.vsize_700, + s.vsize_800, + s.vsize_900, + s.vsize_1000, + s.vsize_1200, + s.vsize_1400, + s.vsize_1600, + s.vsize_1800, + s.vsize_2000, + ] + }; + }); + } + } export default new Statistics(); diff --git a/backend/src/index.ts b/backend/src/index.ts index c88c7e258..f5ef97f24 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -167,6 +167,7 @@ class Server { .get(config.API_ENDPOINT + 'statistics/1m', routes.get1MStatistics.bind(routes)) .get(config.API_ENDPOINT + 'statistics/3m', routes.get3MStatistics.bind(routes)) .get(config.API_ENDPOINT + 'statistics/6m', routes.get6MStatistics.bind(routes)) + .get(config.API_ENDPOINT + 'statistics/1y', routes.get1YStatistics.bind(routes)) ; } } diff --git a/backend/src/interfaces.ts b/backend/src/interfaces.ts index 2d03d5f86..dc01e4f41 100644 --- a/backend/src/interfaces.ts +++ b/backend/src/interfaces.ts @@ -160,6 +160,17 @@ export interface Statistic { vsize_2000: number; } +export interface OptimizedStatistic { + id: number; + added: string; + unconfirmed_transactions: number; + tx_per_second: number; + vbytes_per_second: number; + total_fee: number; + mempool_byte_weight: number; + vsizes: number[]; +} + export interface Outspend { spent: boolean; txid: string; diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 006c5fcab..938592261 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -16,6 +16,7 @@ class Routes { this.cache['1m'] = await statistics.$list1M(); this.cache['3m'] = await statistics.$list3M(); this.cache['6m'] = await statistics.$list6M(); + this.cache['1y'] = await statistics.$list1Y(); console.log('Statistics cache created'); } @@ -44,6 +45,10 @@ class Routes { res.send(this.cache['6m']); } + public get1YStatistics(req, res) { + res.send(this.cache['1y']); + } + public async getRecommendedFees(req, res) { const result = feeApi.getRecommendedFee(); res.send(result); diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html index 9a9f50604..0d8aa15a1 100644 --- a/frontend/src/app/components/statistics/statistics.component.html +++ b/frontend/src/app/components/statistics/statistics.component.html @@ -49,7 +49,7 @@ 6M diff --git a/frontend/src/app/components/statistics/statistics.component.ts b/frontend/src/app/components/statistics/statistics.component.ts index f906644a6..ea1dd38f9 100644 --- a/frontend/src/app/components/statistics/statistics.component.ts +++ b/frontend/src/app/components/statistics/statistics.component.ts @@ -6,7 +6,7 @@ import { of, merge} from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; import { VbytesPipe } from '../../pipes/bytes-pipe/vbytes.pipe'; -import { MempoolStats } from '../../interfaces/node-api.interface'; +import { OptimizedMempoolStats } from '../../interfaces/node-api.interface'; import { WebsocketService } from '../../services/websocket.service'; import { ApiService } from '../../services/api.service'; @@ -22,7 +22,7 @@ export class StatisticsComponent implements OnInit { loading = true; spinnerLoading = false; - mempoolStats: MempoolStats[] = []; + mempoolStats: OptimizedMempoolStats[] = []; mempoolVsizeFeesData: any; mempoolUnconfirmedTransactionsData: any; @@ -62,6 +62,7 @@ export class StatisticsComponent implements OnInit { case '1m': case '3m': case '6m': + case '1y': value = formatDate(value, 'dd/MM', this.locale); } @@ -124,7 +125,7 @@ export class StatisticsComponent implements OnInit { this.route .fragment .subscribe((fragment) => { - if (['2h', '24h', '1w', '1m', '3m', '6m'].indexOf(fragment) > -1) { + if (['2h', '24h', '1w', '1m', '3m', '6m', '1y'].indexOf(fragment) > -1) { this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false }); } }); @@ -158,7 +159,10 @@ export class StatisticsComponent implements OnInit { if (this.radioGroupForm.controls.dateSpan.value === '3m') { return this.apiService.list3MStatistics$(); } - return this.apiService.list6MStatistics$(); + if (this.radioGroupForm.controls.dateSpan.value === '6m') { + return this.apiService.list6MStatistics$(); + } + return this.apiService.list1YStatistics$(); }) ) .subscribe((mempoolStats: any) => { @@ -176,7 +180,7 @@ export class StatisticsComponent implements OnInit { }); } - handleNewMempoolData(mempoolStats: MempoolStats[]) { + handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) { mempoolStats.reverse(); const labels = mempoolStats.map(stats => stats.added); @@ -196,20 +200,14 @@ export class StatisticsComponent implements OnInit { }; } - generateArray(mempoolStats: MempoolStats[]) { - const logFees = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, - 250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000]; - - logFees.reverse(); - + generateArray(mempoolStats: OptimizedMempoolStats[]) { const finalArray: number[][] = []; let feesArray: number[] = []; - logFees.forEach((fee) => { + for (let index = 37; index > -1; index--) { feesArray = []; mempoolStats.forEach((stats) => { - // @ts-ignore - const theFee = stats['vsize_' + fee]; + const theFee = stats.vsizes[index].toString(); if (theFee) { feesArray.push(parseInt(theFee, 10)); } else { @@ -220,7 +218,7 @@ export class StatisticsComponent implements OnInit { feesArray = feesArray.map((value, i) => value + finalArray[finalArray.length - 1][i]); } finalArray.push(feesArray); - }); + } finalArray.reverse(); return finalArray; } diff --git a/frontend/src/app/components/television/television.component.ts b/frontend/src/app/components/television/television.component.ts index 9e20a304b..c775de2bf 100644 --- a/frontend/src/app/components/television/television.component.ts +++ b/frontend/src/app/components/television/television.component.ts @@ -4,7 +4,7 @@ import { VbytesPipe } from '../../pipes/bytes-pipe/vbytes.pipe'; import * as Chartist from 'chartist'; import { WebsocketService } from 'src/app/services/websocket.service'; -import { MempoolStats } from '../../interfaces/node-api.interface'; +import { OptimizedMempoolStats } from '../../interfaces/node-api.interface'; import { StateService } from 'src/app/services/state.service'; import { ApiService } from 'src/app/services/api.service'; @@ -16,7 +16,7 @@ import { ApiService } from 'src/app/services/api.service'; export class TelevisionComponent implements OnInit { loading = true; - mempoolStats: MempoolStats[] = []; + mempoolStats: OptimizedMempoolStats[] = []; mempoolVsizeFeesData: any; mempoolVsizeFeesOptions: any; @@ -88,7 +88,7 @@ export class TelevisionComponent implements OnInit { }); } - handleNewMempoolData(mempoolStats: MempoolStats[]) { + handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) { mempoolStats.reverse(); const labels = mempoolStats.map(stats => stats.added); @@ -103,20 +103,14 @@ export class TelevisionComponent implements OnInit { }; } - generateArray(mempoolStats: MempoolStats[]) { - const logFees = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, - 250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000]; - - logFees.reverse(); - + generateArray(mempoolStats: OptimizedMempoolStats[]) { const finalArray: number[][] = []; let feesArray: number[] = []; - logFees.forEach((fee) => { + for (let index = 37; index > -1; index--) { feesArray = []; mempoolStats.forEach((stats) => { - // @ts-ignore - const theFee = stats['vsize_' + fee]; + const theFee = stats.vsizes[index].toString(); if (theFee) { feesArray.push(parseInt(theFee, 10)); } else { @@ -127,7 +121,7 @@ export class TelevisionComponent implements OnInit { feesArray = feesArray.map((value, i) => value + finalArray[finalArray.length - 1][i]); } finalArray.push(feesArray); - }); + } finalArray.reverse(); return finalArray; } diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index d68fc6e93..ebf8188ca 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -3,51 +3,15 @@ export interface BlockTransaction { f: number; } -export interface MempoolStats { +export interface OptimizedMempoolStats { id: number; added: string; unconfirmed_transactions: number; + tx_per_second: number; vbytes_per_second: number; + total_fee: number; mempool_byte_weight: number; - fee_data: FeeData; - vsize_1: number; - vsize_2: number; - vsize_3: number; - vsize_4: number; - vsize_5: number; - vsize_6: number; - vsize_8: number; - vsize_10: number; - vsize_12: number; - vsize_15: number; - vsize_20: number; - vsize_30: number; - vsize_40: number; - vsize_50: number; - vsize_60: number; - vsize_70: number; - vsize_80: number; - vsize_90: number; - vsize_100: number; - vsize_125: number; - vsize_150: number; - vsize_175: number; - vsize_200: number; - vsize_250: number; - vsize_300: number; - vsize_350: number; - vsize_400: number; - vsize_500: number; - vsize_600: number; - vsize_700: number; - vsize_800: number; - vsize_900: number; - vsize_1000: number; - vsize_1200: number; - vsize_1400: number; - vsize_1600: number; - vsize_1800: number; - vsize_2000: number; + vsizes: number[] | string[]; } interface FeeData { diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 0107074e9..f25dd5e54 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { MempoolStats, BlockTransaction } from '../interfaces/node-api.interface'; +import { OptimizedMempoolStats } from '../interfaces/node-api.interface'; import { Observable } from 'rxjs'; const API_BASE_URL = '/api/v1'; @@ -13,27 +13,31 @@ export class ApiService { private httpClient: HttpClient, ) { } - list2HStatistics$(): Observable { - return this.httpClient.get(API_BASE_URL + '/statistics/2h'); + list2HStatistics$(): Observable { + return this.httpClient.get(API_BASE_URL + '/statistics/2h'); } - list24HStatistics$(): Observable { - return this.httpClient.get(API_BASE_URL + '/statistics/24h'); + list24HStatistics$(): Observable { + return this.httpClient.get(API_BASE_URL + '/statistics/24h'); } - list1WStatistics$(): Observable { - return this.httpClient.get(API_BASE_URL + '/statistics/1w'); + list1WStatistics$(): Observable { + return this.httpClient.get(API_BASE_URL + '/statistics/1w'); } - list1MStatistics$(): Observable { - return this.httpClient.get(API_BASE_URL + '/statistics/1m'); + list1MStatistics$(): Observable { + return this.httpClient.get(API_BASE_URL + '/statistics/1m'); } - list3MStatistics$(): Observable { - return this.httpClient.get(API_BASE_URL + '/statistics/3m'); + list3MStatistics$(): Observable { + return this.httpClient.get(API_BASE_URL + '/statistics/3m'); } - list6MStatistics$(): Observable { - return this.httpClient.get(API_BASE_URL + '/statistics/6m'); + list6MStatistics$(): Observable { + return this.httpClient.get(API_BASE_URL + '/statistics/6m'); + } + + list1YStatistics$(): Observable { + return this.httpClient.get(API_BASE_URL + '/statistics/1y'); } } diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index dc0387209..a9aa42513 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { ReplaySubject, BehaviorSubject, Subject } from 'rxjs'; import { Block } from '../interfaces/electrs.interface'; import { MempoolBlock } from '../interfaces/websocket.interface'; -import { MempoolStats } from '../interfaces/node-api.interface'; +import { OptimizedMempoolStats } from '../interfaces/node-api.interface'; @Injectable({ providedIn: 'root' @@ -13,7 +13,7 @@ export class StateService { conversions$ = new ReplaySubject(1); mempoolBlocks$ = new ReplaySubject(1); txConfirmed = new Subject(); - live2Chart$ = new Subject(); + live2Chart$ = new Subject(); viewFiat$ = new BehaviorSubject(false); isOffline$ = new BehaviorSubject(false); diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 269b25a53..a5b49bc5a 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -18,6 +18,10 @@ $link-hover-decoration: underline !default; @import "~bootstrap/scss/bootstrap"; @import '~tlite/tlite.css'; +html, body { + height: 100%; +} + body { background-color: #11131f; }