Changeset - 04ed5d134973
[Not reviewed]
default
0 6 0
drewp@bigasterisk.com - 3 years ago 2022-04-09 09:47:45
drewp@bigasterisk.com
WIP draw prom metrics on homepage
6 files changed with 122 insertions and 40 deletions:
0 comments (0 inline, 0 general)
bin/homepageConfig
Show inline comments
 
@@ -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"""
light9/web/homepage/ServiceButtonRow.ts
Show inline comments
 
@@ -50,7 +50,7 @@ export class ServiceButtonRow extends Li
 
      <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>
light9/web/homepage/StatsLine.ts
Show inline comments
 
@@ -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<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 @@ export class StatsLine extends LitElemen
 
        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 @@ export class StatsLine extends LitElemen
 
      </td>`;
 
    };
 

	
 
    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`<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 @@ 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`
 
      <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>
 
    `;
 
  }
 
}
light9/web/homepage/StatsProcess.ts
Show inline comments
 
@@ -10,41 +10,53 @@ 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<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);
 
      this.redraw();
 
    };
 
    animate();
 
  }
 
  initCanvas(canvas: HTMLCanvasElement) {
 
    var ctx = canvas.getContext("2d")!;
 

	
 
    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, w, h);
 
      if (!this.data || this.data.time < now - 2) {
 
        return;
 
      }
 
      const dt = now - prev;
 
      prev = now;
 
    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.data.memMb, /*in*/ 20, 600, /*out*/ 3, 30);
 
      revs += dt * remap(this.data.cpuPercent, /*in*/ 0, 100, /*out*/ 4, 120);
 
    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 = w / 2 + rad * Math.cos(revs / 6.28),
 
        y = h / 2 + rad * Math.sin(revs / 6.28);
 
    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();
 
@@ -52,13 +64,10 @@ export class StatsProcess extends LitEle
 
      ctx.arc(x, y, size, 0, 2 * Math.PI);
 
      ctx.fill();
 
      ctx.restore();
 
    };
 
    animate();
 
  }
 

	
 
  updated(changedProperties: any) {
 
    if (changedProperties.has("data")) {
 
      this.shadowRoot!.firstElementChild!.setAttribute("title", `cpu ${this.data.cpuPercent}% mem ${this.data.memMb}MB`);
 
  updated(changedProperties: Map<string, any>) {
 
    if (changedProperties.has("dataTime")) {
 
      this.shadowRoot!.firstElementChild!.setAttribute("title", `cpu ${this.cpu}% mem ${this.mem}MB`);
 
    }
 
  }
 

	
package.json
Show inline comments
 
@@ -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",
pnpm-lock.yaml
Show inline comments
 
@@ -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:
0 comments (0 inline, 0 general)