Skip to content

Commit 6e52191

Browse files
authored
Merge pull request #1658 from Kobzol/artifact-size-ui
Add artifact size UI to compare page
2 parents cf502a0 + 714bb68 commit 6e52191

File tree

10 files changed

+352
-5
lines changed

10 files changed

+352
-5
lines changed

database/src/pool.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ pub trait Connection: Send + Sync {
9191
/// bytes.
9292
async fn record_artifact_size(&self, artifact: ArtifactIdNumber, component: &str, size: u64);
9393

94+
/// Returns the sizes of individual components of a single artifact.
95+
async fn get_artifact_size(&self, aid: ArtifactIdNumber) -> HashMap<String, u64>;
96+
9497
/// Returns vector of bootstrap build times for the given artifacts. The kth
9598
/// element is the minimum build time for the kth artifact in `aids`, across
9699
/// all collections for the artifact, or none if there is no bootstrap data

database/src/pool/postgres.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ pub struct CachedStatements {
337337
insert_runtime_pstat: Statement,
338338
get_runtime_pstat: Statement,
339339
record_artifact_size: Statement,
340+
get_artifact_size: Statement,
340341
}
341342

342343
pub struct PostgresTransaction<'a> {
@@ -530,6 +531,10 @@ impl PostgresConnection {
530531
do update
531532
set size = excluded.size
532533
").await.unwrap(),
534+
get_artifact_size: conn.prepare("
535+
select component, size from artifact_size
536+
where aid = $1
537+
").await.unwrap()
533538
}),
534539
conn,
535540
}
@@ -930,6 +935,18 @@ where
930935
.unwrap();
931936
}
932937

938+
async fn get_artifact_size(&self, aid: ArtifactIdNumber) -> HashMap<String, u64> {
939+
let rows = self
940+
.conn()
941+
.query(&self.statements().get_artifact_size, &[&aid.0])
942+
.await
943+
.unwrap();
944+
945+
rows.into_iter()
946+
.map(|row| (row.get::<_, String>(0), row.get::<_, u32>(1) as u64))
947+
.collect()
948+
}
949+
933950
async fn artifact_id(&self, artifact: &ArtifactId) -> ArtifactIdNumber {
934951
let (name, date, ty) = match artifact {
935952
ArtifactId::Commit(commit) => (

database/src/pool/sqlite.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,18 @@ impl Connection for SqliteConnection {
812812
.unwrap();
813813
}
814814

815+
async fn get_artifact_size(&self, aid: ArtifactIdNumber) -> HashMap<String, u64> {
816+
self.raw_ref()
817+
.prepare("select component, size from artifact_size where aid = ?")
818+
.unwrap()
819+
.query_map(params![&aid.0], |row| {
820+
Ok((row.get::<_, String>(0)?, row.get::<_, u64>(1)?))
821+
})
822+
.unwrap()
823+
.map(|r| r.unwrap())
824+
.collect()
825+
}
826+
815827
async fn get_bootstrap(&self, aids: &[ArtifactIdNumber]) -> Vec<Option<Duration>> {
816828
aids.iter()
817829
.map(|aid| {
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
<script setup lang="ts">
2+
import {ArtifactDescription} from "../types";
3+
import {
4+
diffClass,
5+
formatPercentChange,
6+
formatSize,
7+
isValidValue,
8+
} from "../shared";
9+
import Tooltip from "../tooltip.vue";
10+
11+
const props = defineProps<{
12+
a: ArtifactDescription;
13+
b: ArtifactDescription;
14+
}>();
15+
16+
// Sort binaries first, libraries later. Then within each category, sort alphabetically.
17+
const components = Object.keys(props.a.component_sizes).sort((a, b) => {
18+
const aLib = a.startsWith("lib");
19+
const bLib = b.startsWith("lib");
20+
if (aLib && !bLib) {
21+
return 1;
22+
} else if (!aLib && bLib) {
23+
return -1;
24+
} else {
25+
return a.localeCompare(b);
26+
}
27+
});
28+
29+
function isLibrary(component: string): boolean {
30+
return component.startsWith("lib");
31+
}
32+
33+
function formatName(component: string): string {
34+
if (isLibrary(component)) {
35+
return `${component}.so`;
36+
}
37+
return component;
38+
}
39+
40+
function formatValue(value: number | undefined): string {
41+
if (!isValidValue(value)) {
42+
return "-";
43+
}
44+
return formatSize(value);
45+
}
46+
47+
function formatChangeTitle(
48+
a: number | undefined,
49+
b: number | undefined
50+
): string {
51+
if (!isValidValue(a) || !isValidValue(b)) {
52+
return "";
53+
}
54+
return (b - a).toLocaleString();
55+
}
56+
57+
function formatChange(a: number | undefined, b: number | undefined): string {
58+
if (!isValidValue(a) || !isValidValue(b)) {
59+
return "-";
60+
}
61+
const diff = b - a;
62+
const formatted = formatSize(Math.abs(diff));
63+
if (diff < 0) {
64+
return `-${formatted}`;
65+
}
66+
return formatted;
67+
}
68+
69+
function getClass(a: number | undefined, b: number | undefined): string {
70+
if (!isValidValue(a) || !isValidValue(b) || a == b) {
71+
return "";
72+
}
73+
return diffClass(b - a);
74+
}
75+
76+
function generateTitle(component: string): string {
77+
if (component === "rustc") {
78+
return `Executable of the Rust compiler. A small wrapper that links to librustc_driver.so, which provides most of the compiler logic.`;
79+
} else if (component === "rustdoc") {
80+
return `Executable of rustdoc. Links to librustc_driver.so, which provides most of the compiler logic.`;
81+
} else if (component === "cargo") {
82+
return "Executable of cargo.";
83+
} else if (component === "librustc_driver") {
84+
return `Shared library containing the core implementation of the compiler. It is used by several other tools and binaries.`;
85+
} else if (component === "libLLVM") {
86+
return `Shared library of the LLVM codegen backend. It is used by librustc_driver.so.`;
87+
} else if (component === "libstd") {
88+
return `Shared library containing the Rust standard library. It is used by librustc_driver.so.`;
89+
} else if (component === "libtest") {
90+
return `Shared library containing the Rust test harness.`;
91+
} else {
92+
return ""; // Unknown component
93+
}
94+
}
95+
</script>
96+
97+
<template>
98+
<div class="category-title">Artifact component sizes</div>
99+
<div class="wrapper">
100+
<table>
101+
<thead>
102+
<tr>
103+
<th>Component</th>
104+
<th>Kind</th>
105+
<th class="aligned-header">Before</th>
106+
<th class="aligned-header">After</th>
107+
<th class="aligned-header">Change</th>
108+
<th class="aligned-header">% Change</th>
109+
</tr>
110+
</thead>
111+
<tbody>
112+
<tr v-for="component in components">
113+
<td class="component">
114+
{{ formatName(component) }}
115+
<Tooltip>{{ generateTitle(component) }}</Tooltip>
116+
</td>
117+
<td>
118+
{{ isLibrary(component) ? "Library" : "Binary" }}
119+
</td>
120+
<td>
121+
<div
122+
class="aligned"
123+
:title="a.component_sizes[component].toLocaleString()"
124+
>
125+
{{ formatValue(a.component_sizes[component]) }}
126+
</div>
127+
</td>
128+
<td>
129+
<div
130+
class="aligned"
131+
:title="b.component_sizes[component].toLocaleString()"
132+
>
133+
{{ formatValue(b.component_sizes[component]) }}
134+
</div>
135+
</td>
136+
<td
137+
:class="
138+
getClass(
139+
a.component_sizes[component],
140+
b.component_sizes[component]
141+
)
142+
"
143+
>
144+
<div
145+
class="aligned"
146+
:title="
147+
formatChangeTitle(
148+
a.component_sizes[component],
149+
b.component_sizes[component]
150+
)
151+
"
152+
>
153+
{{
154+
formatChange(
155+
a.component_sizes[component],
156+
b.component_sizes[component]
157+
)
158+
}}
159+
</div>
160+
</td>
161+
<td
162+
:class="
163+
getClass(
164+
a.component_sizes[component],
165+
b.component_sizes[component]
166+
)
167+
"
168+
>
169+
<div class="aligned">
170+
{{
171+
formatPercentChange(
172+
a.component_sizes[component],
173+
b.component_sizes[component]
174+
)
175+
}}
176+
</div>
177+
</td>
178+
</tr>
179+
</tbody>
180+
</table>
181+
</div>
182+
</template>
183+
184+
<style scoped lang="scss">
185+
.wrapper {
186+
display: flex;
187+
justify-content: center;
188+
}
189+
table {
190+
table-layout: fixed;
191+
margin-top: 10px;
192+
193+
td,
194+
th {
195+
text-align: center;
196+
padding: 0.3em;
197+
}
198+
199+
.component {
200+
word-break: break-word;
201+
}
202+
203+
.aligned {
204+
text-align: right;
205+
206+
@media (min-width: 600px) {
207+
width: 120px;
208+
}
209+
}
210+
.aligned-header {
211+
text-align: right;
212+
}
213+
}
214+
</style>

site/frontend/src/pages/compare/page.vue

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
computeRuntimeComparisonsWithNonRelevant,
2929
defaultRuntimeFilter,
3030
} from "./runtime/common";
31+
import ArtifactSizeTable from "./artifact-size/artifact-size-table.vue";
3132
3233
function loadSelectorFromUrl(urlParams: Dict<string>): CompareSelector {
3334
const start = urlParams["start"] ?? "";
@@ -46,6 +47,7 @@ function loadTabFromUrl(urlParams: Dict<string>): Tab | null {
4647
compile: Tab.CompileTime,
4748
runtime: Tab.Runtime,
4849
bootstrap: Tab.Bootstrap,
50+
["artifact-size"]: Tab.ArtifactSize,
4951
};
5052
return tabs[tab] ?? null;
5153
}
@@ -121,10 +123,19 @@ const activeTab = computed((): Tab => {
121123
if (tab.value === Tab.Runtime && !runtimeDataAvailable.value) {
122124
return Tab.CompileTime;
123125
}
126+
if (tab.value === Tab.ArtifactSize && !artifactSizeAvailable.value) {
127+
return Tab.CompileTime;
128+
}
124129
return tab.value;
125130
});
126131
127132
const runtimeDataAvailable = computed(() => runtimeSummary.value !== null);
133+
const artifactSizeAvailable = computed(
134+
() =>
135+
data.value != null &&
136+
(Object.keys(data.value.a.component_sizes).length > 0 ||
137+
Object.keys(data.value.b.component_sizes).length > 0)
138+
);
128139
129140
function changeTab(newTab: Tab) {
130141
tab.value = newTab;
@@ -163,14 +174,17 @@ loadCompareData(selector, loading);
163174
:benchmark-info="info"
164175
/>
165176
</template>
166-
<BootstrapTable v-if="activeTab === Tab.Bootstrap" :data="data" />
167177
<template v-if="runtimeDataAvailable && activeTab === Tab.Runtime">
168178
<RuntimeBenchmarksPage
169179
:data="data"
170180
:selector="selector"
171181
:benchmark-info="info"
172182
/>
173183
</template>
184+
<BootstrapTable v-if="activeTab === Tab.Bootstrap" :data="data" />
185+
<template v-if="artifactSizeAvailable && activeTab === Tab.ArtifactSize">
186+
<ArtifactSizeTable :a="data.a" :b="data.b" />
187+
</template>
174188
</div>
175189
</div>
176190
<br />

site/frontend/src/pages/compare/shared.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export function formatDate(dateString: string): string {
77
date.getUTCDate()
88
)} `;
99
}
10+
1011
export function signIfPositive(pct: number): string {
1112
if (pct >= 0) {
1213
return "+";
@@ -34,3 +35,35 @@ export function diffClass(diff: number): string {
3435
}
3536
return "negative";
3637
}
38+
39+
const KiB = 1024;
40+
const MiB = KiB * 1024;
41+
const GiB = MiB * 1024;
42+
43+
export function formatSize(size: number): string {
44+
if (size >= GiB) {
45+
return `${(size / GiB).toFixed(2)} GiB`;
46+
} else if (size >= MiB) {
47+
return `${(size / MiB).toFixed(2)} MiB`;
48+
} else if (size >= KiB) {
49+
return `${(size / KiB).toFixed(2)} KiB`;
50+
}
51+
return `${size} B`;
52+
}
53+
54+
// Formats a percentual change between two values
55+
export function formatPercentChange(
56+
before: number | undefined,
57+
after: number | undefined,
58+
invalidPlaceholder: string = "-"
59+
): string {
60+
if (!isValidValue(before) || !isValidValue(after)) {
61+
return invalidPlaceholder;
62+
}
63+
return `${(((after - before) / before) * 100).toFixed(3)}%`;
64+
}
65+
66+
// Checks if the value is not undefined and not zero
67+
export function isValidValue(size: number | undefined): boolean {
68+
return size !== undefined && size !== 0;
69+
}

0 commit comments

Comments
 (0)