Permalink
Browse files

feat(dashboard): cpu usage, runned jobs statistics

  • Loading branch information...
jkuri committed Sep 15, 2017
1 parent 02f06b2 commit 5307093a594b62935be29348ba4cd0a35a05421a
@@ -1,4 +1,33 @@
import { JobRun } from './model';
import { format } from 'date-fns';

export function getJobRuns(): Promise<any> {
return new Promise((resolve, reject) => {
new JobRun().fetchAll()
.then(jobRuns => {
if (!jobRuns) {
reject(jobRuns);
} else {
let runs = jobRuns.toJSON();
runs = runs.filter(run => run.status === 'success' || run.status === 'failed')
.reduce((acc, curr) => {
const time = format(new Date(curr.created_at), 'YYYY-MM-DD');
const status = curr.status;

if (!acc[status][time]) {
acc[status][time] = 1;
} else {
acc[status][time] += 1;
}

return acc;
}, { success: {}, failed: {} });

resolve(runs);
}
});
});
}

export function getRun(runId: number): Promise<any> {
return new Promise((resolve, reject) => {
@@ -28,6 +28,7 @@ import {
} from './db/repository';
import { getBuilds, getBuild, getLastBuild } from './db/build';
import { getJob } from './db/job';
import { getJobRuns } from './db/job-run';
import { insertAccessToken, getAccessTokens } from './db/access-token';
import {
updatePermission,
@@ -449,6 +450,18 @@ export function environmentVariableRoutes(): express.Router {
return router;
}

export function statsRoutes(): express.Router {
const router = express.Router();

router.get('/job-runs', (req: express.Request, res: express.Response) => {
getJobRuns()
.then(runs => res.status(200).json({ data: runs }))
.catch(err => res.status(200).json({ status: false }));
});

return router;
}

export function logsRoutes(): express.Router {
const router = express.Router();

@@ -48,6 +48,7 @@ export class ExpressServer implements IExpressServer {
app.use('/api/variables', routes.environmentVariableRoutes());
app.use('/api/logs', routes.logsRoutes());
app.use('/api/keys', routes.keysRoutes());
app.use('/api/stats', routes.statsRoutes());
app.use('/badge', routes.badgeRoutes());
app.use(routes.webRoutes());

@@ -25,14 +25,15 @@ import * as express from 'express';
import { yellow, red, blue, green } from 'chalk';
import { sessionParser } from './server';
import { IMemoryData, memory } from './stats/memory';
import { ICpuData, cpu } from './stats/cpu';

export interface ISocketServerOptions {
port: number;
}

export interface IOutput {
type: string;
data: IMemoryData;
data: IMemoryData | ICpuData;
}

export class SocketServer {
@@ -62,6 +63,9 @@ export class SocketServer {
// send client latest status about jobs
jobEvents.subscribe(event => conn.next(event));

// subscriptions
let statsSub: Subscription;

conn.subscribe(event => {
if (event.type === 'disconnected') {
const index = this.clients.findIndex(client => client.connection === conn);
@@ -167,10 +171,16 @@ export class SocketServer {
break;

case 'subscribeToStats':
memory()
statsSub = Observable.merge(...[memory(), cpu()])
.subscribe(event => conn.next(event));

break;

case 'unsubscribeFromStats':
if (statsSub) {
statsSub.unsubscribe();
}
break;
}
});
});
@@ -0,0 +1,71 @@
import { cpus } from 'os';
import { Observable } from 'rxjs';
import { IOutput } from '../socket';

export interface ICpuData {
idle: number;
load: number;
cores: Object[];
}

export function cpu(): Observable<IOutput> {
return Observable.timer(0, 2000)
.timeInterval()
.mergeMap(() => cpuLoad())
.map((res: ICpuData) => {
return { type: 'cpu', data: res };
});
}

function cpuLoad(): Promise<ICpuData> {
return new Promise(resolve => {
let start = cpuAverage();

setTimeout(() => {
let end = cpuAverage();

let cores = end.cores.map((core, i) => {
let coreIdleDiff = core.idle - start.cores[i].idle;
let coreTotalDiff = end.total - start.total;
let corePercentage = 100 - parseInt(<any>(100 * coreIdleDiff / coreTotalDiff), 10);

return {
idle: 100 - corePercentage,
total: corePercentage
};
});

let idleDiff = end.idle - start.idle;
let totalDiff = end.total - start.total;
let percentage = 100 - parseInt(<any>(100 * idleDiff / totalDiff), 10);
let data = { load: percentage, idle: 100 - percentage, cores: cores };

resolve(data);
}, 2000);
});
}

function cpuAverage(): { idle: number, total: number, cores: { idle: number, total: number }[] } {
let totalIdle = 0;
let totalTick = 0;
let cpuData = cpus();

let data = cpuData.map(core => {
let coreTotal = Object.keys(core.times).reduce((acc, curr) => acc + core.times[curr], 0);
let coreIdle = core.times.idle;

return { idle: coreIdle, total: coreTotal };
}).reduce((acc, curr) => {
acc.idle += curr.idle;
acc.load += curr.total;
acc.cores.push(curr);

return acc;
}, { idle: 0, load: 0, cores: [] });

return {
idle: data.idle / data.cores.length,
total: data.load / data.cores.length,
cores: data.cores
};
}
@@ -14,6 +14,7 @@ import { AccessGuardProvider, AccessGuard } from './services/access-guard.servic
import { AuthServiceProvider } from './services/auth.service';
import { NotificationService } from './services/notification.service';
import { StatsService } from './services/stats.service';
import { WindowService } from './services/window.service';
import { EqualValidator } from './directives/equal-validator.directive';
import { SafeHtmlPipe } from './pipes/safe-html.pipe';
import { ToTimePipe } from './pipes/to-time.pipe';
@@ -34,6 +35,7 @@ import { AppTeamComponent } from './components/app-team';
import { AppLogsComponent } from './components/app-logs';
import { AppDashboardComponent } from './components/app-dashboard';
import { AppLineChartComponent } from './components/app-line-chart';
import { AppProgressChartComponent } from './components/app-progress-chart';


@NgModule({
@@ -55,6 +57,7 @@ import { AppLineChartComponent } from './components/app-line-chart';
AppLogsComponent,
AppDashboardComponent,
AppLineChartComponent,
AppProgressChartComponent,
EqualValidator,
SafeHtmlPipe,
ToTimePipe
@@ -66,12 +69,12 @@ import { AppLineChartComponent } from './components/app-line-chart';
{
path: '',
pathMatch: 'full',
component: AppDashboardComponent,
component: AppBuildsComponent,
canActivate: [AuthGuard]
},
{
path: 'builds',
component: AppBuildsComponent,
path: 'dashboard',
component: AppDashboardComponent,
canActivate: [AuthGuard]
},
{
@@ -136,7 +139,8 @@ import { AppLineChartComponent } from './components/app-line-chart';
AuthGuardProvider,
AccessGuardProvider,
NotificationService,
StatsService
StatsService,
WindowService
],
bootstrap: [ AppComponent ]
})
@@ -24,17 +24,24 @@ <h1>Dashboard</h1>

<div class="columns is-multiline" *ngIf="!loading">

<div class="column is-8">
<div class="column is-7">
<div class="content-box">
<app-line-chart></app-line-chart>
<app-line-chart [data]="runs"></app-line-chart>
</div>
</div>
<div class="column is-4">
<div class="column is-5">
<div class="content-box">

<div class="content-box-header">
<h2>CPU</h2>
<h3>Total CPU Usage</h3>
</div>
<app-progress-chart [percent]="cpuPercent"></app-progress-chart>
</div>
<div class="content-box">
<h2>Memory</h2>
<div class="content-box-header">
<h2>Memory</h2>
<h3>Overall Memory Consumption</h3>
</div>
<div class="info-data-container">
<span class="label-txt">Total</span>
<span class="data-txt">{{ memoryHuman?.total }}</span>
@@ -60,14 +67,6 @@ <h2>Memory</h2>
</div>
</div>

<div class="column is-6">
<div class="content-box"></div>
</div>

<div class="column is-6">
<div class="content-box"></div>
</div>

</div>
</div>
</div>
@@ -12,13 +12,17 @@ export class AppDashboardComponent implements OnInit, OnDestroy {
memory: { total: number, free: number, used: number };
memoryHuman: { total: string, free: string, used: string };
memoryPercentage: string;
runs: { success: {}, failed: {} };
cpuPercent: number;

constructor(private statsService: StatsService) {
this.loading = true;

this.memory = { total: null, free: null, used: null };
this.memoryHuman = { total: null, free: null, used: null };
this.memoryPercentage = '0';
this.runs = { success: {}, failed: {} };
this.cpuPercent = 0;
}

ngOnInit() {
@@ -39,9 +43,13 @@ export class AppDashboardComponent implements OnInit, OnDestroy {
};

this.memoryPercentage = Number(this.memory.used / this.memory.total * 100).toFixed(2);
} else if (e.type === 'cpu') {
this.cpuPercent = e.data.load;
}
});

this.statsService.getJobRuns().then((runs: any) => this.runs = runs);

setTimeout(() => this.statsService.start());
}

@@ -4,8 +4,8 @@
<a class="nav-item nav-logo" routerLink="/">
<img src="images/abstruse-text-logo.svg">
</a>
<a class="nav-item nav-team is-hidden-mobile" routerLink="/builds" routerLinkActive="is-active">
Builds
<a class="nav-item nav-team is-hidden-mobile" routerLink="/dashboard" routerLinkActive="is-active">
Dashboard
</a>
<a class="nav-item nav-team is-hidden-mobile" routerLink="/team" routerLinkActive="is-active" *ngIf="user">
Team
@@ -1 +1,13 @@
<div class="line-chart-top-content">
<h2>Runned Jobs</h2>
<div class="line-chart-legend">
<span class="dot green"></span>
<span>Successful</span>
<span class="dot red"></span>
<span>Failed</span>
</div>
<div class="group-buttons bright small">
<button type="button" class="group-button">{{ minDate }} - {{ maxDate }}</button>
</div>
</div>
<div class="line-chart-container"></div>
Oops, something went wrong.

0 comments on commit 5307093

Please sign in to comment.