changeset 2034:04ed5d134973

WIP draw prom metrics on homepage
author drewp@bigasterisk.com
date Sat, 09 Apr 2022 02:47:45 -0700
parents 224c4a1625d7
children f9faa2ec824f
files bin/homepageConfig light9/web/homepage/ServiceButtonRow.ts light9/web/homepage/StatsLine.ts light9/web/homepage/StatsProcess.ts package.json pnpm-lock.yaml
diffstat 6 files changed, 134 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/bin/homepageConfig	Sat Apr 09 01:40:29 2022 -0700
+++ b/bin/homepageConfig	Sat Apr 09 02:47:45 2022 -0700
@@ -38,8 +38,21 @@
     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"""
--- a/light9/web/homepage/ServiceButtonRow.ts	Sat Apr 09 01:40:29 2022 -0700
+++ b/light9/web/homepage/ServiceButtonRow.ts	Sat Apr 09 02:47:45 2022 -0700
@@ -50,7 +50,7 @@
       <div>
         <div class="left"><a class="big" href="${this.name}/">${this.name}</a></div>
         <div class="window"><button @click="${this.click}">window</button></div>
-        <div><a href="${this.name}/stats/">stats</a></div>
+        <div><a href="${this.name}/metrics">metrics</a></div>
       </div>
 
       <div id="stats"><stats-line name="${this.name}"></div>
--- a/light9/web/homepage/StatsLine.ts	Sat Apr 09 01:40:29 2022 -0700
+++ b/light9/web/homepage/StatsLine.ts	Sat Apr 09 02:47:45 2022 -0700
@@ -2,22 +2,36 @@
 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 @@
   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<string, Metric>();
+      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 @@
         return html`<th>${col}</th>`;
       };
       const td = (col: string): TemplateResult => {
-        const cell = d[col];
+        const cell = byName.get(col)!;
         return html`${drawLevel(cell, path.concat(col))}`;
       };
       return html` <table>
@@ -137,7 +155,7 @@
       </td>`;
     };
 
-    const pmf = (d: any, path: string[]) => {
+    const pmf = (d: Metrics, path: string[]) => {
       return tdWrap(
         table(
           {
@@ -153,7 +171,7 @@
       );
     };
 
-    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 @@
         return html`<stats-process id="proc"></stats-process>`;
       }
       if (typeof d === "object") {
-        if (d.strings) {//} instanceof TemplateResult) {
+        if (d.strings) {
+          //} instanceof TemplateResult) {
           return html`<td class="val">${d}</td>`;
         } else if (d.count !== undefined && d.min !== undefined) {
           return pmf(d, path);
@@ -176,6 +195,34 @@
       }
     };
 
-    return table(this.stats || {}, []);
+const nonBoring = (m: Metric)=>{
+  return !m.name.endsWith('_created') && m.name!= 'python_info'
+}
+
+    // return table(this.stats, []);
+    return html`
+      <table>
+        ${this.stats.filter(nonBoring).map(
+          (row) => html`
+            <tr>
+              <th>${row.name}</th>
+              <td>
+                <table>
+                  ${row.metrics.map(
+                    (v) => html`
+                      <tr>
+                        <td>${JSON.stringify(v.labels)}</td>
+                        <td>${v.value}</td>
+                      </tr>
+                    `
+                  )}
+                </table>
+              </td>
+            </tr>
+          `
+        )}
+      </table>
+      <stats-process id="proc"></stats-process>
+    `;
   }
 }
--- a/light9/web/homepage/StatsProcess.ts	Sat Apr 09 01:40:29 2022 -0700
+++ b/light9/web/homepage/StatsProcess.ts	Sat Apr 09 02:47:45 2022 -0700
@@ -10,55 +10,64 @@
 
 @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<string, any>) {
+    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<string, any>) {
+    if (changedProperties.has("dataTime")) {
+      this.shadowRoot!.firstElementChild!.setAttribute("title", `cpu ${this.cpu}% mem ${this.mem}MB`);
     }
   }
 
--- a/package.json	Sat Apr 09 01:40:29 2022 -0700
+++ b/package.json	Sat Apr 09 02:47:45 2022 -0700
@@ -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",
--- a/pnpm-lock.yaml	Sat Apr 09 01:40:29 2022 -0700
+++ b/pnpm-lock.yaml	Sat Apr 09 02:47:45 2022 -0700
@@ -16,6 +16,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
@@ -37,6 +38,7 @@
   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 @@
       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 @@
       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: