# 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
${this.name}
-
stats
+
metrics
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``; } 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` +
${d}
+ ${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: