# HG changeset patch
# User drewp@bigasterisk.com
# Date 2022-04-09 09:47:45
# Node ID 04ed5d1349736a57d078fdc8ce1a18dfbb6df09d
# Parent 224c4a1625d72ee7317bb11c479053e647ae3318
WIP draw prom metrics on homepage
diff --git a/bin/homepageConfig b/bin/homepageConfig
--- a/bin/homepageConfig
+++ b/bin/homepageConfig
@@ -38,8 +38,21 @@ for role, server in sorted(graph.predica
if not path:
continue
server = server.rstrip('/')
+ if 'collector' in path: continue
location(path, server)
+print('''
+
+ location /collector/metrics {
+ rewrite "/collector(/.*)" "$1" break;
+ proxy_pass http://localhost:8202;
+ }
+ location /collector/ {
+ proxy_pass http://localhost:8302;
+ }
+
+''')
+
showPath = showconfig.showUri().split('/', 3)[-1]
root = showconfig.root()[:-len(showPath)].decode('ascii')
print(f"""
diff --git a/light9/web/homepage/ServiceButtonRow.ts b/light9/web/homepage/ServiceButtonRow.ts
--- a/light9/web/homepage/ServiceButtonRow.ts
+++ b/light9/web/homepage/ServiceButtonRow.ts
@@ -50,7 +50,7 @@ export class ServiceButtonRow extends Li
diff --git a/light9/web/homepage/StatsLine.ts b/light9/web/homepage/StatsLine.ts
--- a/light9/web/homepage/StatsLine.ts
+++ b/light9/web/homepage/StatsLine.ts
@@ -2,22 +2,36 @@ import { css, html, LitElement, Template
import { customElement, property } from "lit/decorators.js";
import { rounding } from "significant-rounding";
import { StatsProcess } from "./StatsProcess";
+export { StatsProcess } from "./StatsProcess";
+import parsePrometheusTextFormat from "parse-prometheus-text-format";
+
+interface Value {
+ labels: { string: string };
+ value: string;
+}
+interface Metric {
+ name: string;
+ help: string;
+ type: "GAUGE" | "SUMMARY" | "COUNTER";
+ metrics: Value[];
+}
+type Metrics = Metric[];
@customElement("stats-line")
export class StatsLine extends LitElement {
@property() name = "?";
- @property() stats: any;
+ @property() stats: Metrics = [];
updated(changedProperties: any) {
changedProperties.forEach((oldValue: any, propName: string) => {
if (propName == "name") {
const reload = () => {
- fetch(this.name + "/stats/?format=json").then((resp) => {
+ fetch(this.name + "/metrics").then((resp) => {
if (resp.ok) {
resp
- .json()
+ .text()
.then((msg) => {
- this.stats = msg;
+ this.stats = parsePrometheusTextFormat(msg) as Metrics;
setTimeout(reload, 1000);
})
.catch((err) => {
@@ -84,8 +98,12 @@ export class StatsLine extends LitElemen
render() {
const now = Date.now() / 1000;
- const table = (d: any, path: string[]): TemplateResult => {
- let cols = Object.keys(d);
+ const table = (d: Metrics, path: string[]): TemplateResult => {
+ const byName = new Map();
+ d.forEach((row) => {
+ byName.set(row.name, row);
+ });
+ let cols = d.map((row) => row.name);
cols.sort();
if (path.length == 0) {
@@ -101,7 +119,7 @@ export class StatsLine extends LitElemen
return html`${col} | `;
};
const td = (col: string): TemplateResult => {
- const cell = d[col];
+ const cell = byName.get(col)!;
return html`${drawLevel(cell, path.concat(col))}`;
};
return html`
@@ -137,7 +155,7 @@ export class StatsLine extends LitElemen
`;
};
- const pmf = (d: any, path: string[]) => {
+ const pmf = (d: Metrics, path: string[]) => {
return tdWrap(
table(
{
@@ -153,7 +171,7 @@ export class StatsLine extends LitElemen
);
};
- const drawLevel = (d: any, path: string[]) => {
+ const drawLevel = (d: Metric, path: string[]) => {
if (path.length == 1 && path[0] === "process") {
const elem = this.shadowRoot!.querySelector("#proc");
if (elem) {
@@ -162,7 +180,8 @@ export class StatsLine extends LitElemen
return html``;
}
if (typeof d === "object") {
- if (d.strings) {//} instanceof TemplateResult) {
+ if (d.strings) {
+ //} instanceof TemplateResult) {
return html`${d} | `;
} else if (d.count !== undefined && d.min !== undefined) {
return pmf(d, path);
@@ -176,6 +195,34 @@ export class StatsLine extends LitElemen
}
};
- return table(this.stats || {}, []);
+const nonBoring = (m: Metric)=>{
+ return !m.name.endsWith('_created') && m.name!= 'python_info'
+}
+
+ // return table(this.stats, []);
+ return html`
+
+ ${this.stats.filter(nonBoring).map(
+ (row) => html`
+
+ ${row.name} |
+
+
+ ${row.metrics.map(
+ (v) => html`
+
+ ${JSON.stringify(v.labels)} |
+ ${v.value} |
+
+ `
+ )}
+
+ |
+
+ `
+ )}
+
+
+ `;
}
}
diff --git a/light9/web/homepage/StatsProcess.ts b/light9/web/homepage/StatsProcess.ts
--- a/light9/web/homepage/StatsProcess.ts
+++ b/light9/web/homepage/StatsProcess.ts
@@ -10,55 +10,64 @@ const remap = (x: number, lo: number, hi
@customElement("stats-process")
export class StatsProcess extends LitElement {
- @property() data: any;
+ @property() dataTime: number = 0; // millis at last data change
+ @property() cpu: number = 12; // process_cpu_seconds_total
+ @property() mem: number = 50000000; // process_resident_memory_bytes
- firstUpdated() {
+ w = 64;
+ h = 64;
+ revs = 0;
+ prev = 0;
+ ctx?: CanvasRenderingContext2D;
+ firstUpdated(c: Map) {
+ super.firstUpdated(c);
// inspired by https://codepen.io/qiruiyin/pen/qOopQx
- var context = this.shadowRoot!.firstElementChild as HTMLCanvasElement;
- var ctx = context.getContext("2d")!,
- w = 64,
- h = 64,
- revs = 0;
-
- context.width = w;
- context.height = h;
-
- let prev = Date.now() / 1000;
+ this.initCanvas(this.shadowRoot!.firstElementChild as HTMLCanvasElement);
+ this.prev = Date.now() / 1000;
var animate = () => {
requestAnimationFrame(animate);
-
- const now = Date.now() / 1000;
- ctx.beginPath();
- // wrong type of fade- never goes to 0
- ctx.fillStyle = "#00000003";
- ctx.fillRect(0, 0, w, h);
- if (!this.data || this.data.time < now - 2) {
- return;
- }
- const dt = now - prev;
- prev = now;
-
- const size = remap(this.data.memMb, /*in*/ 20, 600, /*out*/ 3, 30);
- revs += dt * remap(this.data.cpuPercent, /*in*/ 0, 100, /*out*/ 4, 120);
- const rad = remap(size, /*in*/ 3, 30, /*out*/ 14, 5);
-
- var x = w / 2 + rad * Math.cos(revs / 6.28),
- y = h / 2 + rad * Math.sin(revs / 6.28);
-
- ctx.save();
- ctx.beginPath();
- ctx.fillStyle = "hsl(194, 100%, 42%)";
- ctx.arc(x, y, size, 0, 2 * Math.PI);
- ctx.fill();
- ctx.restore();
+ this.redraw();
};
animate();
}
+ initCanvas(canvas: HTMLCanvasElement) {
+ var ctx = canvas.getContext("2d")!;
- updated(changedProperties: any) {
- if (changedProperties.has("data")) {
- this.shadowRoot!.firstElementChild!.setAttribute("title", `cpu ${this.data.cpuPercent}% mem ${this.data.memMb}MB`);
+ canvas.width = this.w;
+ canvas.height = this.h;
+ }
+ redraw() {
+ if (!this.ctx) return;
+ const now = Date.now() / 1000;
+ const ctx = this.ctx;
+ ctx.beginPath();
+ // wrong type of fade- never goes to 0
+ ctx.fillStyle = "#00000003";
+ ctx.fillRect(0, 0, this.w, this.h);
+ // if (!this.data || this.data.time < now - 2) {
+ // return;
+ // }
+ const dt = now - this.prev;
+ this.prev = now;
+
+ const size = remap(this.mem / 1024 / 1024, /*in*/ 20, 600, /*out*/ 3, 30);
+ this.revs += dt * remap(this.cpu, /*in*/ 0, 100, /*out*/ 4, 120);
+ const rad = remap(size, /*in*/ 3, 30, /*out*/ 14, 5);
+
+ var x = this.w / 2 + rad * Math.cos(this.revs / 6.28),
+ y = this.h / 2 + rad * Math.sin(this.revs / 6.28);
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.fillStyle = "hsl(194, 100%, 42%)";
+ ctx.arc(x, y, size, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.restore();
+ }
+ updated(changedProperties: Map) {
+ if (changedProperties.has("dataTime")) {
+ this.shadowRoot!.firstElementChild!.setAttribute("title", `cpu ${this.cpu}% mem ${this.mem}MB`);
}
}
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"lit": "^2.2.2",
"mocha": "^2.5.3",
"n3": "^1.0.0-alpha",
+ "parse-prometheus-text-format": "^1.1.1",
"pixi.js": "^4.7.3",
"significant-rounding": "^2.0.0",
"tinycolor2": "^1.4.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,6 +16,7 @@ specifiers:
lit: ^2.2.2
mocha: ^2.5.3
n3: ^1.0.0-alpha
+ parse-prometheus-text-format: ^1.1.1
pixi.js: ^4.7.3
significant-rounding: ^2.0.0
tinycolor2: ^1.4.1
@@ -37,6 +38,7 @@ dependencies:
lit: 2.2.2
mocha: 2.5.3
n3: 1.16.0
+ parse-prometheus-text-format: 1.1.1
pixi.js: 4.8.9
significant-rounding: 2.0.0
tinycolor2: 1.4.2
@@ -4808,6 +4810,12 @@ packages:
is-glob: 2.0.1
dev: false
+ /parse-prometheus-text-format/1.1.1:
+ resolution: {integrity: sha512-dBlhYVACjRdSqLMFe4/Q1l/Gd3UmXm8ruvsTi7J6ul3ih45AkzkVpI5XHV4aZ37juGZW5+3dGU5lwk+QLM9XJA==}
+ dependencies:
+ shallow-equal: 1.2.1
+ dev: false
+
/parse-uri/1.0.7:
resolution: {integrity: sha512-eWuZCMKNlVkXrEoANdXxbmqhu2SQO9jUMCSpdbJDObin0JxISn6e400EWsSRbr/czdKvWKkhZnMKEGUwf/Plmg==}
engines: {node: '>= 0.10'}
@@ -5343,6 +5351,10 @@ packages:
kind-of: 6.0.3
dev: false
+ /shallow-equal/1.2.1:
+ resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==}
+ dev: false
+
/shasum-object/1.0.0:
resolution: {integrity: sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==}
dependencies: