UNPKG

dynatrace-api-balancer

Version:

A wrapper around Axios that balances and throttles requests across tenants, clusters and cluster nodes.

203 lines (158 loc) 7.25 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: lib/Host.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: lib/Host.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>'use strict'; const axios = require('axios').default; const { RequestQueue } = require('./RequestQueue'); const Request = require('./Request.js'); const Throttle = require('./Throttle.js'); const DirectAPIRequest = require('./DirectAPIRequest.js'); const Host = function (hostName, mainQueue, { requestLimit, maxQueueSize, maxQueueTime, reqRateLimit, maxRetries }) { const self = this; const globalQueue = mainQueue; const requester = new DirectAPIRequest({ maxRetries }); const issuedList = new RequestQueue(requestLimit, maxQueueTime); const localQueue = new RequestQueue(maxQueueSize, maxQueueTime); const throttle = new Throttle(reqRateLimit, 60 * 1000); function reset() { localQueue.clear(); issuedList.clear(); throttle.reset(); // Ensure that we keep emptying the global queue. setTimeout(acceptNext, throttle.nextSlot); } async function issue(request) { // Relevant for determining how much time we have for retries. request.options.createTime = request.createTime; // Let the caller know about our progress. request.emitter.emit("progress", "start"); // Wait for the tenant's throttle's permission. await request.tenant.throttle.permit(); requester.fetch(request.options) .then(data => { // Release this request from the queue (with the data). // The queue will trigger the callback to the caller. issuedList.release(request, null, data); // Let the caller know about our progress. request.emitter.emit("progress", "end"); }) .catch(error => { // Release this request from the queue (with the error). issuedList.release(request, error); // Let the caller know about this unfortunate ending. request.emitter.emit("error", error.message); }) .finally(() => { // Ensure that we keep emptying the global queue. setImmediate(acceptNext); }); } /** * Refills the bucket completely. * @param {Object} fruit [description] * @param {String} fruit.name [description] * @return {String} [description] */ function accept(request, forced) { // Schedule a recursive call to keep consuming from the queues. if (localQueue.length > 0 || globalQueue.length > 0) setTimeout(acceptNext, throttle.nextSlot); // If we're full and not forced to accept, politely decline. if (localQueue.isFull &amp;&amp; !forced) return false; // We declined the request. // Add the request (if any) to the end of the queue because // we'll be issuing the longest-waiting request first. if (request) { localQueue.place(request); } // If the throttle is choking, or if there are too many requests still running, // we schedule a call to keep consuming from the local and global queues. if (throttle.getWaitTime() > 0 || issuedList.isFull) { setTimeout(acceptNext, throttle.nextSlot); return true; // We queued the request. } // Take the first request waiting in line, if any, but if the global queue has an // older one for us, take that one. The first request in our queue is our oldest. const oldestTime = localQueue.length > 0 ? localQueue.peek().queueTime : null; request = globalQueue.takeNext(self, oldestTime) || localQueue.takeNext(); if (!request) return false; // No more requests waiting that we should or could handle. // Tell the request we're handling it, and the throttle we're consuming one. throttle.consume(); issuedList.place(request); request.setHost(hostName); setImmediate(issue, request); return true; // We issued the request. } const publicIF = { get name() { return hostName; }, get localQueue() { return localQueue; }, get issuedList() { return issuedList; }, isAlive: function(tenant) { return axios.get( (tenant.protocol || "https") + "://" + hostName + (tenant.port ? ":" + tenant.port : "") + (tenant.url || "") + "/api/v1/time", { responseType: 'blob' } // It's an epoch time, a string we'll be getting back. ); }, reset: reset, accept: accept, get availability() { // Return a measure of our availability as a number ranging // from minus infinity to the (positive) size of our throttle. // If our waiting list is full, we are infinitely unavailable. if (localQueue.isFull) return Number.NEGATIVE_INFINITY; // We do have space, but our unavailability is proportional // to the length of our waiting list. if (localQueue.length > 0) return -1 * localQueue.length; // Our availability is proportional to what's left in the // throttle. If we have outstanding requests, with a limit // to them, then that proportionally reduces our availability. return issuedList.length > 0 ? throttle.remainder * (requestLimit - issuedList.length) / requestLimit : throttle.remainder; } }; // Easy for everybody to call to keep on consuming queued requests. const acceptNext = publicIF.accept.bind(publicIF); return publicIF; }; module.exports = Host;</code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="BalancedAPIRequest.html">BalancedAPIRequest</a></li><li><a href="CancellableEventEmitter.html">CancellableEventEmitter</a></li><li><a href="CancellablePromise.html">CancellablePromise</a></li><li><a href="DirectAPIRequest.html">DirectAPIRequest</a></li><li><a href="Throttle.html">Throttle</a></li></ul><h3><a href="global.html">Global</a></h3> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.7</a> on Sun Dec 19 2021 10:28:39 GMT-0600 (Central Standard Time) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>