fix time display bug and type metrics (#2490)

* fix time display bug and type metrics

* change metrics response

* nullable temp

* rename percentage used

* match frontend types

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Matt Hill
2023-11-01 16:30:54 -06:00
committed by GitHub
parent 9c6dcc4a43
commit b597d0366a
8 changed files with 284 additions and 210 deletions

View File

@@ -203,7 +203,7 @@ frontend/config.json: $(GIT_HASH_FILE) frontend/config-sample.json
jq '.useMocks = false' frontend/config-sample.json | jq '.gitHash = "$(shell cat GIT_HASH.txt)"' > frontend/config.json
frontend/patchdb-ui-seed.json: frontend/package.json
jq '."ack-welcome" = $(shell yq '.version' frontend/package.json)' frontend/patchdb-ui-seed.json > ui-seed.tmp
jq '."ack-welcome" = $(shell jq '.version' frontend/package.json)' frontend/patchdb-ui-seed.json > ui-seed.tmp
mv ui-seed.tmp frontend/patchdb-ui-seed.json
patch-db/client/node_modules: patch-db/client/package.json

View File

@@ -360,60 +360,44 @@ impl<'de> Deserialize<'de> for GigaBytes {
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsGeneral {
#[serde(rename = "Temperature")]
temperature: Option<Celsius>,
pub temperature: Option<Celsius>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsMemory {
#[serde(rename = "Percentage Used")]
pub percentage_used: Percentage,
#[serde(rename = "Total")]
pub total: MebiBytes,
#[serde(rename = "Available")]
pub available: MebiBytes,
#[serde(rename = "Used")]
pub used: MebiBytes,
#[serde(rename = "Swap Total")]
pub swap_total: MebiBytes,
#[serde(rename = "Swap Free")]
pub swap_free: MebiBytes,
#[serde(rename = "Swap Used")]
pub swap_used: MebiBytes,
pub zram_total: MebiBytes,
pub zram_available: MebiBytes,
pub zram_used: MebiBytes,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsCpu {
#[serde(rename = "User Space")]
user_space: Percentage,
#[serde(rename = "Kernel Space")]
kernel_space: Percentage,
#[serde(rename = "I/O Wait")]
wait: Percentage,
#[serde(rename = "Idle")]
percentage_used: Percentage,
idle: Percentage,
#[serde(rename = "Usage")]
usage: Percentage,
user_space: Percentage,
kernel_space: Percentage,
wait: Percentage,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsDisk {
#[serde(rename = "Size")]
size: GigaBytes,
#[serde(rename = "Used")]
percentage_used: Percentage,
used: GigaBytes,
#[serde(rename = "Available")]
available: GigaBytes,
#[serde(rename = "Percentage Used")]
used_percentage: Percentage,
capacity: GigaBytes,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Metrics {
#[serde(rename = "General")]
general: MetricsGeneral,
#[serde(rename = "Memory")]
memory: MetricsMemory,
#[serde(rename = "CPU")]
cpu: MetricsCpu,
#[serde(rename = "Disk")]
disk: MetricsDisk,
}
@@ -739,7 +723,7 @@ async fn get_cpu_info(last: &mut ProcStat) -> Result<MetricsCpu, Error> {
kernel_space: Percentage((new.system() - last.system()) as f64 * 100.0 / total_diff as f64),
idle: Percentage((new.idle - last.idle) as f64 * 100.0 / total_diff as f64),
wait: Percentage((new.iowait - last.iowait) as f64 * 100.0 / total_diff as f64),
usage: Percentage((new.used() - last.used()) as f64 * 100.0 / total_diff as f64),
percentage_used: Percentage((new.used() - last.used()) as f64 * 100.0 / total_diff as f64),
};
*last = new;
Ok(res)
@@ -752,8 +736,8 @@ pub struct MemInfo {
buffers: Option<u64>,
cached: Option<u64>,
slab: Option<u64>,
swap_total: Option<u64>,
swap_free: Option<u64>,
zram_total: Option<u64>,
zram_free: Option<u64>,
}
#[instrument(skip_all)]
pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
@@ -765,8 +749,8 @@ pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
buffers: None,
cached: None,
slab: None,
swap_total: None,
swap_free: None,
zram_total: None,
zram_free: None,
};
fn get_num_kb(l: &str) -> Result<u64, Error> {
let e = Error::new(
@@ -791,8 +775,8 @@ pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
_ if entry.starts_with("Buffers") => mem_info.buffers = Some(get_num_kb(entry)?),
_ if entry.starts_with("Cached") => mem_info.cached = Some(get_num_kb(entry)?),
_ if entry.starts_with("Slab") => mem_info.slab = Some(get_num_kb(entry)?),
_ if entry.starts_with("SwapTotal") => mem_info.swap_total = Some(get_num_kb(entry)?),
_ if entry.starts_with("SwapFree") => mem_info.swap_free = Some(get_num_kb(entry)?),
_ if entry.starts_with("SwapTotal") => mem_info.zram_total = Some(get_num_kb(entry)?),
_ if entry.starts_with("SwapFree") => mem_info.zram_free = Some(get_num_kb(entry)?),
_ => (),
}
}
@@ -808,24 +792,24 @@ pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
let buffers = ensure_present(mem_info.buffers, "Buffers")?;
let cached = ensure_present(mem_info.cached, "Cached")?;
let slab = ensure_present(mem_info.slab, "Slab")?;
let swap_total_k = ensure_present(mem_info.swap_total, "SwapTotal")?;
let swap_free_k = ensure_present(mem_info.swap_free, "SwapFree")?;
let zram_total_k = ensure_present(mem_info.zram_total, "SwapTotal")?;
let zram_free_k = ensure_present(mem_info.zram_free, "SwapFree")?;
let total = MebiBytes(mem_total as f64 / 1024.0);
let available = MebiBytes(mem_available as f64 / 1024.0);
let used = MebiBytes((mem_total - mem_free - buffers - cached - slab) as f64 / 1024.0);
let swap_total = MebiBytes(swap_total_k as f64 / 1024.0);
let swap_free = MebiBytes(swap_free_k as f64 / 1024.0);
let swap_used = MebiBytes((swap_total_k - swap_free_k) as f64 / 1024.0);
let zram_total = MebiBytes(zram_total_k as f64 / 1024.0);
let zram_available = MebiBytes(zram_free_k as f64 / 1024.0);
let zram_used = MebiBytes((zram_total_k - zram_free_k) as f64 / 1024.0);
let percentage_used = Percentage((total.0 - available.0) / total.0 * 100.0);
Ok(MetricsMemory {
percentage_used,
total,
available,
used,
swap_total,
swap_free,
swap_used,
zram_total,
zram_available,
zram_used,
})
}
@@ -849,10 +833,10 @@ async fn get_disk_info() -> Result<MetricsDisk, Error> {
let total_percentage = total_used as f64 / total_size as f64 * 100.0f64;
Ok(MetricsDisk {
size: GigaBytes(total_size as f64 / 1_000_000_000.0),
capacity: GigaBytes(total_size as f64 / 1_000_000_000.0),
used: GigaBytes(total_used as f64 / 1_000_000_000.0),
available: GigaBytes(total_available as f64 / 1_000_000_000.0),
used_percentage: Percentage(total_percentage as f64),
percentage_used: Percentage(total_percentage as f64),
})
}

View File

@@ -11,15 +11,11 @@
</ion-header>
<ion-content class="ion-padding with-widgets">
<skeleton-list *ngIf="loading" [groups]="2"></skeleton-list>
<div id="metricSection">
<ng-container *ngIf="!loading">
<ion-item-group>
<!-- <ion-item-divider>Time</ion-item-divider> -->
<ion-item *ngIf="now$ | async as now; else timeLoading">
<ion-item>
<ion-label>
<h1>System Time</h1>
<ng-container *ngIf="now$ | async as now; else timeLoading">
<h2>
<ion-text style="color: white">
<b>{{ now.value | date:'MMMM d, y, h:mm a z':'UTC' }}</b>
@@ -30,25 +26,20 @@
NTP not synced, time could be wrong
</ion-text>
</p>
</ion-label>
<ion-note slot="end" class="metric-note"></ion-note>
</ion-item>
</ng-container>
<ng-template #timeLoading>
<ion-item>
<ion-label>
<h1>System Time</h1>
<p>Loading...</p>
<h2>Loading...</h2>
</ng-template>
</ion-label>
<ion-note slot="end" class="metric-note"></ion-note>
</ion-item>
</ng-template>
<ion-item>
<ion-label>
<h1>System Uptime</h1>
<h2>
<ion-text style="color: white">
<ng-container *ngIf="uptime$ | async as uptime">
<ng-container *ngIf="uptime$ | async as uptime; else uptimeLoading">
<b>{{ uptime.days }}</b>
Days,
<b>{{ uptime.hours }}</b>
@@ -58,27 +49,117 @@
<b>{{ uptime.seconds }}</b>
Seconds
</ng-container>
<ng-template #uptimeLoading>Loading...</ng-template>
</ion-text>
</h2>
</ion-label>
</ion-item>
</ion-item-group>
<ion-item-group
*ngFor="let metricGroup of metrics | keyvalue : asIsOrder"
>
<ion-item-divider>{{ metricGroup.key }}</ion-item-divider>
<ion-item
*ngFor="let metric of metricGroup.value | keyvalue : asIsOrder"
>
<ion-label>{{ metric.key }}</ion-label>
<ion-note *ngIf="metric.value" slot="end" class="metric-note">
<ion-text style="color: white">
{{ metric.value.value }} {{ metric.value.unit }}
</ion-text>
<ng-container *ngIf="metrics$ | async as metrics; else loading">
<ion-item-group *ngIf="metrics.general as general">
<ion-item-divider>General</ion-item-divider>
<ion-item>
<ion-label>Temperature</ion-label>
<ion-note slot="end">
<ng-container *ngIf="general.temperature; else noTemp">
{{ general.temperature.value }} &deg;C
</ng-container>
<ng-template #noTemp>N/A</ng-template>
</ion-note>
</ion-item>
</ion-item-group>
<ion-item-group *ngIf="metrics.memory as memory">
<ion-item-divider>Memory</ion-item-divider>
<ion-item>
<ion-label>Percentage Used</ion-label>
<ion-note slot="end">{{ memory['percentage-used'].value }} %</ion-note>
</ion-item>
<ion-item>
<ion-label>Total</ion-label>
<ion-note slot="end">
<ion-text>{{ memory.total.value }} MiB</ion-text>
</ion-note>
</ion-item>
<ion-item>
<ion-label>Used</ion-label>
<ion-note slot="end">
<ion-text>{{ memory.used.value }} MiB</ion-text>
</ion-note>
</ion-item>
<ion-item>
<ion-label>Available</ion-label>
<ion-note slot="end">{{ memory.available.value }} MiB</ion-note>
</ion-item>
<ion-item>
<ion-label>zram Used</ion-label>
<ion-note slot="end">{{ memory['zram-used'].value }} MiB</ion-note>
</ion-item>
<ion-item>
<ion-label>zram Total</ion-label>
<ion-note slot="end">{{ memory['zram-total'].value }} MiB</ion-note>
</ion-item>
<ion-item>
<ion-label>zram Available</ion-label>
<ion-note slot="end">{{ memory['zram-available'].value }} MiB</ion-note>
</ion-item>
</ion-item-group>
<ion-item-group *ngIf="metrics.cpu as cpu">
<ion-item-divider>CPU</ion-item-divider>
<ion-item>
<ion-label>Percentage Used</ion-label>
<ion-note slot="end">{{ cpu['percentage-used'].value }} %</ion-note>
</ion-item>
<ion-item>
<ion-label>User Space</ion-label>
<ion-note slot="end">
<ion-text>{{ cpu['user-space'].value }} %</ion-text>
</ion-note>
</ion-item>
<ion-item>
<ion-label>Kernel Space</ion-label>
<ion-note slot="end">
<ion-text>{{ cpu['kernel-space'].value }} %</ion-text>
</ion-note>
</ion-item>
<ion-item>
<ion-label>Idle</ion-label>
<ion-note slot="end">{{ cpu.idle.value }} %</ion-note>
</ion-item>
<ion-item>
<ion-label>I/O Wait</ion-label>
<ion-note slot="end">{{ cpu.wait.value }} %</ion-note>
</ion-item>
</ion-item-group>
<ion-item-group *ngIf="metrics.disk as disk">
<ion-item-divider>Disk</ion-item-divider>
<ion-item>
<ion-label>Percentage Used</ion-label>
<ion-note slot="end">{{ disk['percentage-used'].value }} %</ion-note>
</ion-item>
<ion-item>
<ion-label>Capacity</ion-label>
<ion-note slot="end">
<ion-text>{{ disk.capacity.value }} GB</ion-text>
</ion-note>
</ion-item>
<ion-item>
<ion-label>Used</ion-label>
<ion-note slot="end">
<ion-text>{{ disk.used.value }} GB</ion-text>
</ion-note>
</ion-item>
<ion-item>
<ion-label>Available</ion-label>
<ion-note slot="end">{{ disk.available.value }} GB</ion-note>
</ion-item>
</ion-item-group>
</ng-container>
</div>
<ng-template #loading>
<skeleton-list [groups]="4"></skeleton-list>
</ng-template>
</ion-content>

View File

@@ -1,3 +1,4 @@
.metric-note {
ion-note {
font-size: 16px;
color: white;
}

View File

@@ -3,6 +3,7 @@ import { Metrics } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { TimeService } from 'src/app/services/time-service'
import { pauseFor, ErrorToastService } from '@start9labs/shared'
import { Subject } from 'rxjs'
@Component({
selector: 'server-metrics',
@@ -10,9 +11,8 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared'
styleUrls: ['./server-metrics.page.scss'],
})
export class ServerMetricsPage {
loading = true
going = false
metrics: Metrics = {}
metrics$ = new Subject<Metrics>()
readonly now$ = this.timeService.now$
readonly uptime$ = this.timeService.uptime$
@@ -25,19 +25,7 @@ export class ServerMetricsPage {
async ngOnInit() {
await this.getMetrics()
let headersCount = 0
let rowsCount = 0
Object.values(this.metrics).forEach(groupVal => {
headersCount++
Object.keys(groupVal).forEach(_ => {
rowsCount++
})
})
const height = headersCount * 54 + rowsCount * 50 + 24 // extra 24 for room at the bottom
const elem = document.getElementById('metricSection')
if (elem) elem.style.height = `${height}px`
this.startDaemon()
this.loading = false
}
ngOnDestroy() {
@@ -59,7 +47,8 @@ export class ServerMetricsPage {
private async getMetrics(): Promise<void> {
try {
this.metrics = await this.embassyApi.getServerMetrics({})
const metrics = await this.embassyApi.getServerMetrics({})
this.metrics$.next(metrics)
} catch (e: any) {
this.errToast.present(e)
this.stopDaemon()

View File

@@ -835,85 +835,79 @@ export module Mock {
export function getServerMetrics() {
return {
Group1: {
Metric1: {
value: Math.random(),
unit: 'mi/b',
general: {
temperature: {
value: '66.8',
unit: '°C',
},
Metric2: {
value: Math.random(),
},
memory: {
'percentage-used': {
value: '30.7',
unit: '%',
},
Metric3: {
value: 10.1,
total: {
value: '31971.10',
unit: 'MiB',
},
available: {
value: '22150.66',
unit: 'MiB',
},
used: {
value: '8784.97',
unit: 'MiB',
},
'zram-total': {
value: '7992.00',
unit: 'MiB',
},
'zram-available': {
value: '7882.50',
unit: 'MiB',
},
'zram-used': {
value: '109.50',
unit: 'MiB',
},
},
cpu: {
'percentage-used': {
value: '8.4',
unit: '%',
},
'user-space': {
value: '7.0',
unit: '%',
},
'kernel-space': {
value: '1.4',
unit: '%',
},
wait: {
value: '0.5',
unit: '%',
},
idle: {
value: '91.1',
unit: '%',
},
},
Group2: {
Hmmmm1: {
value: 22.2,
unit: 'mi/b',
disk: {
capacity: {
value: '1851.60',
unit: 'GB',
},
Hmmmm2: {
value: 50,
unit: '%',
used: {
value: '859.02',
unit: 'GB',
},
Hmmmm3: {
value: 10.1,
unit: '%',
available: {
value: '992.59',
unit: 'GB',
},
},
Group3: {
Hmmmm1: {
value: Math.random(),
unit: 'mi/b',
},
Hmmmm2: {
value: 50,
unit: '%',
},
Hmmmm3: {
value: 10.1,
unit: '%',
},
},
Group4: {
Hmmmm1: {
value: Math.random(),
unit: 'mi/b',
},
Hmmmm2: {
value: 50,
unit: '%',
},
Hmmmm3: {
value: 10.1,
unit: '%',
},
},
Group5: {
Hmmmm1: {
value: Math.random(),
unit: 'mi/b',
},
Hmmmm2: {
value: 50,
unit: '%',
},
Hmmmm3: {
value: 10.1,
unit: '%',
},
Hmmmm4: {
value: Math.random(),
unit: 'mi/b',
},
Hmmmm5: {
value: 50,
unit: '%',
},
Hmmmm6: {
value: 10.1,
'percentage-used': {
value: '46.4',
unit: '%',
},
},

View File

@@ -301,12 +301,36 @@ export interface ActionResponse {
qr: boolean
}
interface MetricData {
value: string
unit: string
}
export interface Metrics {
[key: string]: {
[key: string]: {
value: string | number | null
unit?: string
general: {
temperature: MetricData | null
}
memory: {
total: MetricData
'percentage-used': MetricData
used: MetricData
available: MetricData
'zram-total': MetricData
'zram-used': MetricData
'zram-available': MetricData
}
cpu: {
'percentage-used': MetricData
idle: MetricData
'user-space': MetricData
'kernel-space': MetricData
wait: MetricData
}
disk: {
capacity: MetricData
'percentage-used': MetricData
used: MetricData
available: MetricData
}
}

View File

@@ -3,13 +3,14 @@ import { map, shareReplay, startWith, switchMap } from 'rxjs/operators'
import { PatchDB } from 'patch-db-client'
import { DataModel } from './patch-db/data-model'
import { ApiService } from './api/embassy-api.service'
import { combineLatest, from, interval } from 'rxjs'
import { combineLatest, interval, of } from 'rxjs'
@Injectable({
providedIn: 'root',
})
export class TimeService {
private readonly time$ = from(this.apiService.getSystemTime({})).pipe(
private readonly time$ = of({}).pipe(
switchMap(() => this.apiService.getSystemTime({})),
switchMap(({ now, uptime }) => {
const current = new Date(now).valueOf()
return interval(1000).pipe(