1/*
2 * Copyright (C) 2929, Unicode, Inc.
3 * For terms of use, see http://www.unicode.org/terms_of_use.html
4 */
5
6const express = require('express');
7const process = require('process');
8const server = express();
9const client = require('prom-client');
10const bent = require('bent');
11const getJson = bent('json');
12const config = require('./config.json');
13const {register} = client; // global registry
14
15const items = {
16  responses: new client.Counter({
17    name: 'surveytool_exporter_responses',
18    help: 'Number of pages served by this exporter'
19  }),
20  oks: new client.Counter({
21    name: 'surveytool_exporter_oks',
22    help: 'Number of OK fetches',
23    labelNames: ['instance']
24  }),
25  fails: new client.Counter({
26    name: 'surveytool_exporter_fails',
27    help: 'Number of failed fetches',
28    labelNames: ['instance']
29  }),
30  ok: new client.Gauge({
31    name: 'surveytool_ok',
32    help: '1 if the surveytool is ok, otherwise 0',
33    labelNames: ['instance']
34  }),
35  isSetup: new client.Gauge({
36    name: 'surveytool_setup',
37    help: '1 if the surveytool is setup, otherwise 0',
38    labelNames: ['instance']
39  }),
40  isBusted: new client.Gauge({
41    name: 'surveytool_busted',
42    help: '1 if the surveytool is busted, otherwise 0',
43    labelNames: ['instance'/*, 'err'*/]
44  }),
45  fetchTime: new client.Gauge({
46    name: 'surveytool_fetchTime',
47    help: 'time of successful fetch',
48    labelNames: ['instance']
49  }),
50  fetchErr: new client.Gauge({
51    name: 'surveytool_fetchErr',
52    help: 'error code on failed fetch, or 200',
53    labelNames: ['instance'/*, 'err'*/]
54  }),
55  pages: new client.Gauge({
56    name: 'surveytool_pages',
57    help: 'page count',
58    labelNames: ['instance']
59  }),
60  users: new client.Gauge({
61    name: 'surveytool_users',
62    help: 'user count',
63    labelNames: ['instance']
64  }),
65  stamp: new client.Gauge({
66    name: 'surveytool_stamp',
67    help: 'survey running stamp',
68    labelNames: ['instance' /*,
69  'phase', 'sysprocs', 'environment', 'currev', 'newVersion'*/]
70  }),
71  memtotal: new client.Gauge({
72    name: 'surveytool_memtotal',
73    help: 'total memory in process',
74    labelNames: ['instance']
75  }),
76  memfree: new client.Gauge({
77    name: 'surveytool_memfree',
78    help: 'total free memory',
79    labelNames: ['instance']
80  }),
81  dbused: new client.Gauge({
82    name: 'surveytool_dbused',
83    help: 'db queries used',
84    labelNames: ['instance']
85  }),
86  sysload: new client.Gauge({
87    name: 'surveytool_sysload',
88    help: 'system load, if available',
89    labelNames: ['instance']
90  }),
91};
92
93async function update(e) {
94  const [instance, url] = e;
95  try {
96    const res = await getJson(url);
97    items.oks.inc({instance});
98    items.fetchErr.set({ instance }, 200);
99    items.fetchTime.set({ instance }, new Date().getTime()/1000);
100    items.ok.set({instance}, Number(res.SurveyOK));
101    items.isSetup.set({instance}, Number(res.isSetup));
102    if(res.status) {
103      const{phase, memtotal, sysprocs, isBusted, isUnofficial, lockOut,
104        users, uptime, memfree, environment, pages, specialHeader, currev,
105        dbopen, surveyRunningStamp, guests, dbused,sysload, isSetup, newVersion} = res.status;
106        items.pages.set({instance}, Number(pages));
107        items.users.set({instance}, Number(users));
108        items.memtotal.set({instance}, Number(memtotal));
109        items.memfree.set({instance}, Number(memfree));
110        items.dbused.set({instance}, Number(dbused));
111        items.sysload.set({instance}, Number(sysload));
112        items.stamp.set({instance /*,
113        phase, sysprocs, environment, currev, newVersion*/},
114          surveyRunningStamp);
115        if(isBusted) {
116          items.isBusted.set({instance/*, err: isBusted*/}, Number(1));
117        } else {
118          items.isBusted.set({instance/*, err: (res.err || '')*/}, Number(res.isBusted));
119        }
120    } else {
121      items.isBusted.set({instance/*, err: (res.err || '')*/}, Number(res.isBusted));
122    }
123  } catch(ex) {
124    items.fails.inc({instance});
125    items.fetchErr.set({ instance /*, err: ex.toString()*/ }, 999);
126  }
127}
128
129async function updateAll() {
130  return Promise.all(Object.entries(config.instances)
131    .map(e => update(e)));
132}
133
134server.get('/metrics', async (req, res) => {
135  items.responses.inc();
136  res.contentType(register.contentType);
137  await updateAll();
138  res.end(register.metrics());
139});
140
141const port = process.env.PORT || config.port || 3000;
142
143server.listen(port, () => {
144  console.log('ST exporter listening on port ' + port);
145});