Changeset - 623836db99af
[Not reviewed]
default
0 1 1
drewp@bigasterisk.com - 20 months ago 2023-06-08 22:05:59
drewp@bigasterisk.com
fix ts warning
2 files changed with 20 insertions and 21 deletions:
0 comments (0 inline, 0 general)
light9/web/lib/parse-prometheus-text-format.d.ts
Show inline comments
 
new file 100644
 
declare module "parse-prometheus-text-format" {
 
  function parsePrometheusTextFormat(s: string): any;
 
  export default parsePrometheusTextFormat;
 
}
light9/web/metrics/StatsLine.ts
Show inline comments
 
@@ -27,59 +27,59 @@ function nonBoring(m: Metric) {
 
    !m.name.startsWith("python_gc_") &&
 
    m.name != "python_info" &&
 
    m.name != "process_max_fds" &&
 
    m.name != "process_virtual_memory_bytes" &&
 
    m.name != "process_resident_memory_bytes" &&
 
    m.name != "process_start_time_seconds" &&
 
    m.name != "process_cpu_seconds_total"
 
  );
 
}
 

	
 
@customElement("stats-line")
 
export class StatsLine extends LitElement {
 
  @property() name = "?";
 
  @property() stats: Metrics = [];
 

	
 
  prevCpuNow = 0;
 
  prevCpuTotal = 0;
 
  @property() cpu = 0;
 
  @property() mem = 0;
 

	
 
  updated(changedProperties: any) {
 
    changedProperties.forEach((oldValue: any, propName: string) => {
 
      if (propName == "name") {
 
        const reload = () => {
 
          fetch("/service/"+this.name + "/metrics").then((resp) => {
 
          fetch("/service/" + this.name + "/metrics").then((resp) => {
 
            if (resp.ok) {
 
              resp
 
                .text()
 
                .then((msg) => {
 
                  this.stats = parsePrometheusTextFormat(msg) as Metrics;
 
                  this.extractProcessStats(this.stats);
 
                  setTimeout(reload, 1000);
 
                })
 
                .catch((err) => {
 
                  log(`${this.name} failing`, err)
 
                  log(`${this.name} failing`, err);
 
                  setTimeout(reload, 1000);
 
                });
 
            } else {
 
              if (resp.status == 502) {
 
                setTimeout(reload, 5000);
 
              }
 
              // 404: likely not mapped to a responding server
 
            }
 
          });
 
        };
 
        reload();
 
      }
 
    });
 
  }
 
  extractProcessStats(stats: Metrics) {
 
    stats.forEach((row: Metric) => {
 
      if (row.name == "process_resident_memory_bytes") {
 
        this.mem = parseFloat(row.metrics[0].value!) / 1024 / 1024;
 
      }
 
      if (row.name == "process_cpu_seconds_total") {
 
        const now = Date.now() / 1000;
 
        const cpuSecondsTotal = parseFloat(row.metrics[0].value!);
 
        this.cpu = (cpuSecondsTotal - this.prevCpuTotal) / (now - this.prevCpuNow);
 
        this.prevCpuTotal = cpuSecondsTotal;
 
@@ -174,49 +174,48 @@ export class StatsLine extends LitElemen
 
      });
 
    }
 

	
 
    const th = (col: string): TemplateResult => {
 
      return html`<th>${col}</th>`;
 
    };
 
    const td = (col: string): TemplateResult => {
 
      const cell = byName.get(col)!;
 
      return html`${this.drawLevel(cell, path.concat(col))}`;
 
    };
 
    return html` <table>
 
      <tr>
 
        ${cols.map(th)}
 
      </tr>
 
      <tr>
 
        ${cols.map(td)}
 
      </tr>
 
    </table>`;
 
  }
 

	
 
  drawLevel(d: Metric, path: string[]) {
 
    return html`[NEW ${JSON.stringify(d)} ${path}]`;
 
  }
 

	
 

	
 
  valueDisplay(m: Metric, v: Value): TemplateResult {
 
    if (m.type == "GAUGE") {
 
      return html`${v.value}`;
 
    } else if (m.type == "COUNTER") {
 
      return html`${v.value}`;
 
    } else if (m.type == "HISTOGRAM") {
 
      return this.histoDisplay(v.buckets!);
 
    } else if (m.type == "UNTYPED") {
 
      return html`${v.value}`;
 
    } else if (m.type == "SUMMARY") {
 
      if (!v.count) {
 
        return html`err: summary without count`;
 
      }
 
      return html`n=${v.count} percall=${((v.count && v.sum ? v.sum / v.count : 0) * 1000).toPrecision(3)}ms`;
 
    } else {
 
      throw m.type;
 
    }
 
  }
 

	
 
  private histoDisplay(b: { [value: string]: string }) {
 
    const lines: TemplateResult[] = [];
 
    let firstLevel;
 
    let lastLevel;
 
    let prev = 0;
 
@@ -228,79 +227,75 @@ export class StatsLine extends LitElemen
 
      let count = parseFloat(b[level]);
 
      let delta = count - prev;
 
      prev = count;
 
      if (delta > maxDelta) maxDelta = delta;
 
    }
 
    prev = 0;
 
    const maxBarH = 30;
 
    for (let level in b) {
 
      let count = parseFloat(b[level]);
 
      let delta = count - prev;
 
      prev = count;
 
      let levelf = parseFloat(level);
 
      const h = clamp((delta / maxDelta) * maxBarH, 1, maxBarH);
 
      lines.push(
 
        html`<div
 
          title="bucket=${level} count=${count}"
 
          style="background: yellow; margin-right: 1px; width: 8px; height: ${h}px; display: inline-block"
 
        ></div>`
 
      );
 
    }
 
    return html`${firstLevel} ${lines} ${lastLevel}`;
 
  }
 

	
 
  tightLabel(labs: { [key: string]: string }): string {
 
    const d: { [key: string]: string } = {}
 
    const d: { [key: string]: string } = {};
 
    for (let k in labs) {
 
      if (k == 'app_name') continue;
 
      if (k == 'output') continue;
 
      if (k=='status_code'&&labs[k]=="200") continue;
 
      d[k] = labs[k]
 
      if (k == "app_name") continue;
 
      if (k == "output") continue;
 
      if (k == "status_code" && labs[k] == "200") continue;
 
      d[k] = labs[k];
 
    }
 
    const ret = JSON.stringify(d)
 
    return ret == "{}" ? "" : ret
 
    const ret = JSON.stringify(d);
 
    return ret == "{}" ? "" : ret;
 
  }
 
  tightMetric(name: string): string {
 
    return name
 
    .replace('starlette', '⭐')
 
    .replace("_request" ,"_req")
 
    .replace("_duration" ,"_dur")
 
    .replace('_seconds', '_s')
 
    return name.replace("starlette", "⭐").replace("_request", "_req").replace("_duration", "_dur").replace("_seconds", "_s");
 
  }
 
  render() {
 
    const now = Date.now() / 1000;
 

	
 
    const displayedStats = this.stats.filter(nonBoring);
 
    return html`
 
      <div>
 
        <table>
 
          ${displayedStats.map(
 
      (row, rowNum) => html`
 
            (row, rowNum) => html`
 
              <tr>
 
                <th>${this.tightMetric(row.name)}</th>
 
                <td>
 
                  <table>
 
                    ${row.metrics.map(
 
        (v) => html`
 
                      (v) => html`
 
                        <tr>
 
                          <td>${this.tightLabel(v.labels)}</td>
 
                          <td>${this.valueDisplay(row, v)}</td>
 
                        </tr>
 
                      `
 
      )}
 
                    )}
 
                  </table>
 
                </td>
 
                ${rowNum == 0
 
          ? html`
 
                  ? html`
 
                      <td rowspan="${displayedStats.length}">
 
                        <stats-process id="proc" cpu="${this.cpu}" mem="${this.mem}"></stats-process>
 
                      </td>
 
                    `
 
          : ""}
 
                  : ""}
 
              </tr>
 
            `
 
    )}
 
          )}
 
        </table>
 
      </div>
 
    `;
 
  }
 
}
0 comments (0 inline, 0 general)