-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #615 from molgenis/feat/metrics
feat: use actuator/metrics for data points
- Loading branch information
Showing
12 changed files
with
440 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
armadillo/src/main/java/org/molgenis/armadillo/info/FileMetrics.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package org.molgenis.armadillo.info; | ||
|
||
import io.micrometer.common.lang.NonNull; | ||
import io.micrometer.core.instrument.Gauge; | ||
import io.micrometer.core.instrument.MeterRegistry; | ||
import io.micrometer.core.instrument.binder.MeterBinder; | ||
import java.io.File; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class FileMetrics implements MeterBinder { | ||
|
||
private static final String PATH = System.getProperty("user.dir"); | ||
|
||
@Override | ||
public void bindTo(@NonNull MeterRegistry registry) { | ||
File folder = new File(PATH); | ||
File[] listOfFiles = folder.listFiles(); | ||
|
||
int fileCount = 0; | ||
int dirCount = 0; | ||
|
||
if (listOfFiles != null) { | ||
for (File file : listOfFiles) { | ||
if (file.isFile()) { | ||
fileCount++; | ||
} else if (file.isDirectory()) { | ||
dirCount++; | ||
} | ||
} | ||
} | ||
|
||
Gauge.builder("user.files.count", fileCount, Integer::doubleValue) | ||
.description("Number of files in the current directory") | ||
.baseUnit("files") | ||
.tags("path", PATH) | ||
.register(registry); | ||
|
||
Gauge.builder("user.directories.count", dirCount, Integer::doubleValue) | ||
.description("Number of directories in the current directory") | ||
.baseUnit("directories") | ||
.tags("path", PATH) | ||
.register(registry); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
<template> | ||
<div class="row"> | ||
<div class="col mt-3" v-if="isLoading"> | ||
<LoadingSpinner /> | ||
</div> | ||
<div class="col" v-else> | ||
<div class="row"> | ||
<div class="col-sm-3"> | ||
<SearchBar id="searchbox" v-model="filterValue" /> | ||
</div> | ||
<div class="col"> | ||
<button | ||
class="btn btn-primary float-end" | ||
v-if="metrics" | ||
@click="downloadMetrics" | ||
> | ||
<i class="bi bi-box-arrow-down"></i> | ||
Download metrics | ||
</button> | ||
</div> | ||
</div> | ||
<table class="table"> | ||
<thead> | ||
<tr> | ||
<th scope="col">#</th> | ||
<th>key</th> | ||
<th>statistic</th> | ||
<th>value</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<ActuatorItem | ||
v-for="(metric, path, index) in metrics" | ||
:key="index" | ||
:data="metric" | ||
:name="path" | ||
/> | ||
</tbody> | ||
</table> | ||
<hr /> | ||
<summary> | ||
<h3>Other Actuator links</h3> | ||
<details> | ||
<table> | ||
<thead> | ||
<tr> | ||
<td>key</td> | ||
<td>href</td> | ||
<td>templated</td> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr v-for="(item, key) in actuator" :key="key"> | ||
<td>{{ key }}</td> | ||
<td v-if="item.templated">{{ item.href }}</td> | ||
<td v-if="!item.templated"> | ||
<a :href="item.href" target="_new">{{ item.href }}</a> | ||
</td> | ||
<td>{{ item.templated }}</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
</details> | ||
</summary> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { getActuator, getMetricsAll } from "@/api/api"; | ||
import { ref, watch } from "vue"; | ||
import { Metrics, HalLinks } from "@/types/api"; | ||
import { ObjectWithStringKey } from "@/types/types"; | ||
import { objectDeepCopy } from "@/helpers/utils"; | ||
import ActuatorItem from "./ActuatorItem.vue"; | ||
import SearchBar from "@/components/SearchBar.vue"; | ||
import LoadingSpinner from "./LoadingSpinner.vue"; | ||
const actuator = ref<HalLinks>(); | ||
const metrics = ref<Metrics>([]); | ||
const isLoading = ref<boolean>(true); | ||
const loadActuator = async () => { | ||
let result = (await getActuator())["_links"]; | ||
let list = []; | ||
for (let key in result) { | ||
// Add key to each item for further usage | ||
const item = result[key]; | ||
item["key"] = key; | ||
list.push(result[key]); | ||
} | ||
actuator.value = list; | ||
}; | ||
const loadMetrics = async () => { | ||
metrics.value = await getMetricsAll(); | ||
// preload search values | ||
filteredLines(); | ||
isLoading.value = false; | ||
}; | ||
loadMetrics(); | ||
loadActuator(); | ||
function downloadJSON(filename: string) { | ||
const cleanedUp = removeFields(metrics.value); | ||
const dataStr = | ||
"data:text/json;charset=utf-8," + | ||
encodeURIComponent(JSON.stringify(cleanedUp)); | ||
const downloadAnchorNode = document.createElement("a"); | ||
downloadAnchorNode.setAttribute("href", dataStr); | ||
downloadAnchorNode.setAttribute("download", filename + ".json"); | ||
document.body.appendChild(downloadAnchorNode); // required for firefox | ||
downloadAnchorNode.click(); | ||
setTimeout(() => downloadAnchorNode.remove(), 10); | ||
} | ||
function downloadMetrics() { | ||
downloadJSON("armadillo-metrics-" + new Date().toISOString()); | ||
} | ||
const filterValue = ref(""); | ||
watch(filterValue, (_newVal, _oldVal) => filteredLines()); | ||
const FIELD_DISPLAY = "_display"; | ||
const SEARCH_TEXT_FIELDS = "searchWords"; | ||
function concatValues(obj: any): string { | ||
let result = ""; | ||
for (const key in obj) { | ||
if (typeof obj[key] === "object" && obj[key] !== null) { | ||
result += concatValues(obj[key]); | ||
} else { | ||
result += obj[key]; | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
* Filter metrics on search value | ||
* | ||
* We add: | ||
* - string search field for matching | ||
* - booelan display field for storing matched | ||
*/ | ||
function filteredLines() { | ||
const filterOn: string = filterValue.value.toLowerCase(); | ||
for (let [_key, value] of Object.entries(metrics.value)) { | ||
if (!value[SEARCH_TEXT_FIELDS]) { | ||
value[SEARCH_TEXT_FIELDS] = concatValues(value).toLowerCase(); | ||
} | ||
const searchWords: string = value[SEARCH_TEXT_FIELDS]; | ||
value[FIELD_DISPLAY] = filterOn === "" || searchWords.includes(filterOn); | ||
} | ||
} | ||
/** | ||
* Remove added fields for searching. | ||
* | ||
* @param json | ||
*/ | ||
function removeFields(json: Metrics) { | ||
const result: Metrics = objectDeepCopy<Metrics>(json); | ||
for (let [_key, value] of Object.entries(result)) { | ||
const wrapper: ObjectWithStringKey = value; | ||
delete wrapper[SEARCH_TEXT_FIELDS]; | ||
delete wrapper[FIELD_DISPLAY]; | ||
} | ||
return result; | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<template> | ||
<tr v-if="data._display" v-for="(v, key) in data.measurements" :key="key"> | ||
<td scope="col">{{ key }}</td> | ||
<td :title="data.description"> | ||
<span> | ||
{{ data.name }} | ||
<i v-if="data.description" class="bi bi-info-circle-fill"></i> | ||
</span> | ||
</td> | ||
<td>{{ v.statistic }}</td> | ||
<td v-if="data.baseUnit === 'bytes'"> | ||
{{ convertBytes(v.value) }} | ||
</td> | ||
<td v-else>{{ v.value }} {{ data.baseUnit }}</td> | ||
</tr> | ||
<tr v-if="data._display"> | ||
<td colspan="5"> | ||
<summary> | ||
<details> | ||
<pre> | ||
{{ JSON.stringify(data, null, 3) }} | ||
</pre> | ||
</details> | ||
</summary> | ||
</td> | ||
</tr> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
const props = defineProps({ | ||
name: { | ||
type: String, | ||
required: true, | ||
}, | ||
data: { | ||
type: Object, | ||
required: true, | ||
}, | ||
}); | ||
/** | ||
* Convert given bytes to 2 digits precision round exponent version string. | ||
* @param bytes number | ||
*/ | ||
function convertBytes(bytes: number): string { | ||
const units = ["bytes", "KB", "MB", "GB", "TB", "EB"]; | ||
let unitIndex = 0; | ||
while (bytes >= 1024 && unitIndex < units.length - 1) { | ||
bytes /= 1024; | ||
unitIndex++; | ||
} | ||
return `${bytes.toFixed(2)} ${units[unitIndex]}`; | ||
} | ||
</script> |
Oops, something went wrong.