import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
+import * as _ from 'lodash';
import { BsModalService } from 'ngx-bootstrap/modal';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ToastrModule } from 'ngx-toastr';
});
});
+ describe('custom row comparators', () => {
+ const expectCorrectComparator = (statsAttribute: string) => {
+ const mockPool = (v) => ({ stats: { [statsAttribute]: { latest: v } } });
+ const columnDefinition = _.find(
+ component.columns,
+ (column) => column.prop === `stats.${statsAttribute}.rates`
+ );
+ expect(columnDefinition.comparator(undefined, undefined, mockPool(2), mockPool(1))).toBe(1);
+ expect(columnDefinition.comparator(undefined, undefined, mockPool(1), mockPool(2))).toBe(-1);
+ };
+
+ it('compares read bytes correctly', () => {
+ expectCorrectComparator('rd_bytes');
+ });
+
+ it('compares write bytes correctly', () => {
+ expectCorrectComparator('wr_bytes');
+ });
+ });
+
describe('transformPoolsData', () => {
it('transforms pools data correctly', () => {
const pools = [
{
stats: {
- bytes_used: { latest: 5, rate: 0, series: [] },
- max_avail: { latest: 15, rate: 0, series: [] },
- rd_bytes: { latest: 6, rate: 4, series: [[0, 2], [1, 6]] }
+ bytes_used: { latest: 5, rate: 0, rates: [] },
+ max_avail: { latest: 15, rate: 0, rates: [] },
+ rd_bytes: { latest: 6, rate: 4, rates: [[0, 2], [1, 6]] }
},
pg_status: { 'active+clean': 8, down: 2 }
}
cdIsBinary: true,
pg_status: '8 active+clean, 2 down',
stats: {
- bytes_used: { latest: 5, rate: 0, series: [] },
- max_avail: { latest: 15, rate: 0, series: [] },
- rd: { latest: 0, rate: 0, series: [] },
- rd_bytes: { latest: 6, rate: 4, series: [2, 6] },
- wr: { latest: 0, rate: 0, series: [] },
- wr_bytes: { latest: 0, rate: 0, series: [] }
+ bytes_used: { latest: 5, rate: 0, rates: [] },
+ max_avail: { latest: 15, rate: 0, rates: [] },
+ rd: { latest: 0, rate: 0, rates: [] },
+ rd_bytes: { latest: 6, rate: 4, rates: [2, 6] },
+ wr: { latest: 0, rate: 0, rates: [] },
+ wr_bytes: { latest: 0, rate: 0, rates: [] }
},
usage: 0.25
}
cdIsBinary: true,
pg_status: '',
stats: {
- bytes_used: { latest: 0, rate: 0, series: [] },
- max_avail: { latest: 0, rate: 0, series: [] },
- rd: { latest: 0, rate: 0, series: [] },
- rd_bytes: { latest: 0, rate: 0, series: [] },
- wr: { latest: 0, rate: 0, series: [] },
- wr_bytes: { latest: 0, rate: 0, series: [] }
+ bytes_used: { latest: 0, rate: 0, rates: [] },
+ max_avail: { latest: 0, rate: 0, rates: [] },
+ rd: { latest: 0, rate: 0, rates: [] },
+ rd_bytes: { latest: 0, rate: 0, rates: [] },
+ wr: { latest: 0, rate: 0, rates: [] },
+ wr_bytes: { latest: 0, rate: 0, rates: [] }
},
usage: 0
}
}
ngOnInit() {
+ const compare = (prop: string, pool1: Pool, pool2: Pool) =>
+ _.get(pool1, prop) > _.get(pool2, prop) ? 1 : -1;
this.columns = [
{
prop: 'pool_name',
flexGrow: 3
},
{
- prop: 'stats.rd_bytes.series',
+ prop: 'stats.rd_bytes.rates',
name: this.i18n('Read bytes'),
+ comparator: (_valueA, _valueB, rowA: Pool, rowB: Pool) =>
+ compare('stats.rd_bytes.latest', rowA, rowB),
cellTransformation: CellTemplate.sparkline,
flexGrow: 3
},
{
- prop: 'stats.wr_bytes.series',
+ prop: 'stats.wr_bytes.rates',
name: this.i18n('Write bytes'),
+ comparator: (_valueA, _valueB, rowA: Pool, rowB: Pool) =>
+ compare('stats.wr_bytes.latest', rowA, rowB),
cellTransformation: CellTemplate.sparkline,
flexGrow: 3
},
transformPoolsData(pools: any) {
const requiredStats = ['bytes_used', 'max_avail', 'rd_bytes', 'wr_bytes', 'rd', 'wr'];
- const emptyStat = { latest: 0, rate: 0, series: [] };
+ const emptyStat = { latest: 0, rate: 0, rates: [] };
_.forEach(pools, (pool: Pool) => {
pool['pg_status'] = this.transformPgStatus(pool['pg_status']);
pool['usage'] = avail > 0 ? stats.bytes_used.latest / avail : avail;
['rd_bytes', 'wr_bytes'].forEach((stat) => {
- pool.stats[stat].series = pool.stats[stat].series.map((point) => point[1]);
+ pool.stats[stat].rates = pool.stats[stat].rates.map((point) => point[1]);
});
pool.cdIsBinary = true;
});
s[stat_name] = {
'latest': stat_series[0][1],
'rate': get_rate(stat_series),
- 'series': [i for i in stat_series]
+ 'rates': get_rates_from_data(stat_series)
}
pool['stats'] = s
pools_w_stats.append(pool)
:return: the derivative of mgr.get_counter()
:rtype: list[tuple[int, float]]"""
data = mgr.get_counter(svc_type, svc_name, path)[path]
- if not data:
- return [(0, 0.0)]
- if len(data) == 1:
- return [(data[0][0], 0.0)]
- return [(data2[0], differentiate(data1, data2)) for data1, data2 in pairwise(data)]
+ return get_rates_from_data(data)
@classmethod
def get_rate(cls, svc_type, svc_name, path):
}
+def get_rates_from_data(data):
+ """
+ >>> get_rates_from_data([])
+ [(0, 0.0)]
+ >>> get_rates_from_data([[1, 42]])
+ [(1, 0.0)]
+ >>> get_rates_from_data([[0, 100], [2, 101], [3, 100], [4, 100]])
+ [(2, 0.5), (3, 1.0), (4, 0.0)]
+ """
+ if not data:
+ return [(0, 0.0)]
+ if len(data) == 1:
+ return [(data[0][0], 0.0)]
+ return [(data2[0], differentiate(data1, data2)) for data1, data2 in pairwise(data)]
+
+
def differentiate(data1, data2):
"""
>>> times = [0, 2]
>>> values = [100, 101]
>>> differentiate(*zip(times, values))
0.5
+ >>> times = [0, 2]
+ >>> values = [100, 99]
+ >>> differentiate(*zip(times, values))
+ 0.5
"""
- return (data2[1] - data1[1]) / float(data2[0] - data1[0])
+ return abs((data2[1] - data1[1]) / float(data2[0] - data1[0]))