1{
2  "status": "success",
3  "data": {
4    "resultType": "vector",
5    "result": [
6      ${
7        [1].map(item => {
8          const dc = 'dc1';
9          const generateTargets = function(num) {
10            // Seed faker by the number of results we want to make it deterministic
11            // here and in other correlated endpoints.
12            fake.seed(num);
13            return range(num).map(i => {
14              const nspace = i === 0 ? `default` : `${fake.hacker.noun()}-ns-${i}`;
15              return {
16                Name: `service-${fake.random.number({min:0, max:99})}`,
17                Datacenter: `${dc}`,
18                Namespace: `${nspace}`
19              }
20            })
21          };
22
23          // little helper to get a deterministic number from the target service
24          // name string. NOTE: this should be the same as in metrics-proxy/.../query
25          // endpoint so metrics match what is requested.
26          const hashStr = function(s) {
27            for(var i = 0, h = 0xdeadbeef; i < s.length; i++)
28                h = Math.imul(h ^ s.charCodeAt(i), 2654435761);
29            return (h ^ h >>> 16) >>> 0;
30          };
31
32          const randExp = function(max, lambda) {
33            return (-Math.log(1-(1-Math.exp(-lambda))*Math.random())/lambda) * max;
34          };
35
36          const q = location.search.query;
37          let type = 'service';
38          let proto = 'tcp';
39
40          // Match the relabel arguments since "downstream" appears in both
41          // "service" and "upstream" type queries' metric names while
42          // "upstream" appears in downstream query metric names (confusingly).
43          if (q.match('consul_destination_service=')) {
44            type = "downstream";
45          } else if (q.match('consul_upstream_service')) {
46            type = "upstream";
47          }
48
49          if (q.match('envoy_http_')) {
50            proto = 'http';
51          }
52
53          // NOTE!!! The logic below to pick the upstream/downstream service
54          // names must exactly match the logic in internal/ui/service-topology/_
55          // If you change this, change it there too!
56
57          // Pick a number of down/upstreams to return based on the cookie variable.
58          // If you change anything about this variable or it's default, you'll need
59          // to change the topology endpoint to match.
60          let numResults = 1;
61          if (type === 'upstream') {
62            numResults = parseInt(env('CONSUL_UPSTREAM_COUNT', 3));
63          }
64          if (type === 'downstream') {
65            numResults = parseInt(env('CONSUL_DOWNSTREAM_COUNT', 5));
66          }
67
68          // Figure out the actual name for the target service
69          var targetService = "invalid-local-cluster";
70          var m = q.match(/consul_source_service="([^"]*)"/);
71          if (m && m.length >= 2 && m[1] != "") {
72            targetService = m[1];
73          }
74          m = q.match(/consul_destination_service="([^"]*)"/);
75          if (type == "downstream" && m && m.length >= 2 && m[1] != "") {
76            // downstreams don't have the same selector for the main service
77            // name.
78            targetService = m[1];
79          }
80
81          // Figure out the actual namespace for the target service
82          var targetNS = "invalid-local-ns";
83          var m = q.match(/consul_source_namespace="([^"]*)"/);
84          if (m && m.length >= 2 && m[1] != "") {
85            targetNS = m[1];
86          }
87          m = q.match(/consul_destination_namespace="([^"]*)"/);
88          if (type == "downstream" && m && m.length >= 2 && m[1] != "") {
89            // downstreams don't have the same selector for the main service
90            // name.
91            targetNS = m[1];
92          }
93
94          // Figure out the actual datacenter for the target service
95          var targetDC = "invalid-local-dc";
96          var m = q.match(/consul_source_datacenter="([^"]*)"/);
97          if (m && m.length >= 2 && m[1] != "") {
98            targetDC = m[1];
99          }
100          m = q.match(/consul_destination_datacenter="([^"]*)"/);
101          if (type == "downstream" && m && m.length >= 2 && m[1] != "") {
102            // downstreams don't have the same selector for the main service
103            // name.
104            targetDC = m[1];
105          }
106
107          var serviceNames = [];
108          switch(type) {
109            case 'downstream': // fallthrough
110            case 'upstream':
111              targets = generateTargets(numResults);
112              break;
113            default:
114              // fallthrough
115            case 'service':
116              targets = [targetService];
117              break;
118          }
119
120          let serviceProto = 'tcp';
121          // Randomly pick the serviceProtocol which will affect which types of
122          // stats we return for downstream clusters. But we need it to be
123          // deterministic for a given service name so that all the downstream
124          // stats are consistently typed.
125          fake.seed(hashStr(targetService))
126          if (fake.random.number(1) > 0.5) {
127            serviceProto = 'http';
128          }
129
130          // For up/downstreams only return HTTP metrics half of the time.
131
132          // For upstreams it's based on the upstream's protocol which might be
133          // mixed so alternate protocols for upstreams.
134          if (type === "upstream") {
135            // Pretend all odd service indexes are tcp and even are http
136            const wantMod = proto === 'tcp' ? 1 : 0;
137            targets = targets.filter((item, i) => i % 2 === wantMod);
138          }
139          // For downstreams it's based on the target's protocol which we
140          // don't really know but all downstreams should be the same type
141          // so only return metrics for that protocol.
142          if (type === 'downstream' && proto === 'http' && serviceProto !== 'http') {
143            targets = [];
144          }
145
146          // Work out which metric is being queried to make them more realistic.
147          let max = 100;
148          switch(proto) {
149            case 'http':
150              if (q.match('envoy_response_code_class="5"')) {
151                // It's error rate make it a percentage
152                max = 30;
153              } else if (q.match("rq_completed")) {
154                // Requests per second
155                max = 1000;
156              } else if (q.match("quantile\\(0.99")) {
157                // 99 percentile time in ms make it longer than 50 percentile
158                max = 5000;
159              } else if (q.match("quantile\\(0.5")) {
160                // 50th percentile
161                max = 500;
162              }
163              break;
164            case 'tcp':
165              if (q.match('cx_total')) {
166                // New conns per second
167                max = 100;
168              } else if (q.match('cx_rx_bytes')) {
169                // inbound data rate tends to be lower than outbound
170                max = 0.5 * 1e9;
171              } else if (q.match('cx_tx_bytes')) {
172                // inbound data rate
173                max = 1e9;
174              }
175              // no route/connect failed are OK with default 0-100
176              break;
177          }
178
179
180          // Now generate the data points
181          return targets.map((item, i) => {
182            let metric = `{}`;
183            switch(type) {
184              case 'upstream':
185                metric = `{"consul_upstream_service": "${item.Name}", "consul_upstream_datacenter": "${targetDC}", "consul_upstream_namespace": "${targetNS}"}`;
186                break;
187              case "downstream":
188                metric = `{"consul_source_service": "${item.Name}", "consul_source_datacenter": "${targetDC}", "consul_source_namespace": "${targetNS}"}`;
189                break;
190            }
191            const timestamp = Date.now() / 1000;
192            let value = randExp(max, 20);
193
194            // prometheus can sometimes generate NaN and undefined strings, so
195            // replicate that randomly
196            const num = fake.random.number({min: 0, max: 10});
197            switch(true) {
198              case num > 8:
199                value = 'NaN';
200                break;
201              case num > 5:
202                value = 'undefined';
203                break;
204            }
205            return `{
206              "metric": ${metric},
207              "value": [
208                ${timestamp},
209                "${value}"
210              ]
211            }`;
212          })
213        })
214      }
215    ]
216  }
217}
218