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