@google-cloud/profiler
Advanced tools
Comparing version 0.1.6 to 0.1.7
@@ -31,3 +31,6 @@ /** | ||
heapMaxStackDepth?: number; | ||
backoffMillis?: number; | ||
backoffMultiplier?: number; | ||
initialBackoffMillis?: number; | ||
backoffCapMillis?: number; | ||
serverBackoffCapMillis?: number; | ||
} | ||
@@ -48,3 +51,6 @@ export interface ProfilerConfig extends AuthenticationConfig { | ||
heapMaxStackDepth: number; | ||
backoffMillis: number; | ||
initialBackoffMillis: number; | ||
backoffCapMillis: number; | ||
backoffMultiplier: number; | ||
serverBackoffCapMillis: number; | ||
} | ||
@@ -59,3 +65,6 @@ export declare const defaultConfig: { | ||
heapMaxStackDepth: number; | ||
backoffMillis: number; | ||
initialBackoffMillis: number; | ||
backoffCapMillis: number; | ||
backoffMultiplier: number; | ||
serverBackoffCapMillis: number; | ||
}; |
@@ -18,2 +18,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var parseDuration = require('parse-duration'); | ||
var common = require('@google-cloud/common'); | ||
@@ -23,3 +24,3 @@ var extend = require('extend'); | ||
exports.defaultConfig = { | ||
logLevel: 1, | ||
logLevel: 2, | ||
serviceContext: {}, | ||
@@ -31,4 +32,10 @@ disableHeap: false, | ||
heapMaxStackDepth: 64, | ||
backoffMillis: 5 * 60 * 1000 | ||
initialBackoffMillis: 1000, | ||
backoffCapMillis: parseDuration('1h'), | ||
backoffMultiplier: 1.3, | ||
// This is the largest duration for setTimeout which does not cause it to | ||
// run immediately. | ||
// https://nodejs.org/dist/latest-v9.x/docs/api/timers.html#timers_settimeout_callback_delay_args. | ||
serverBackoffCapMillis: 2147483647 | ||
}; | ||
//# sourceMappingURL=config.js.map |
@@ -61,2 +61,3 @@ "use strict"; | ||
var common = require('@google-cloud/common'); | ||
var pjson = require('../../package.json'); | ||
/** | ||
@@ -162,8 +163,17 @@ * @return value of metadata field. | ||
return __awaiter(this, void 0, void 0, function () { | ||
var normalizedConfig; | ||
var normalizedConfig, e_1, logger; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, initConfig(config)]; | ||
case 0: | ||
_a.trys.push([0, 2, , 3]); | ||
return [4 /*yield*/, initConfig(config)]; | ||
case 1: | ||
normalizedConfig = _a.sent(); | ||
return [3 /*break*/, 3]; | ||
case 2: | ||
e_1 = _a.sent(); | ||
logger = new common.logger({ level: common.logger.LEVELS[config.logLevel || 2], tag: pjson.name }); | ||
logger.error("Could not start profiler: " + e_1); | ||
return [2 /*return*/]; | ||
case 3: | ||
profiler = new profiler_1.Profiler(normalizedConfig); | ||
@@ -170,0 +180,0 @@ profiler.start(); |
@@ -39,2 +39,15 @@ import { Common } from '../third_party/types/common-types'; | ||
/** | ||
* Class which tracks how long to wait before the next retry and can be | ||
* used to get this backoff. | ||
*/ | ||
export declare class Retryer { | ||
readonly initialBackoffMillis: number; | ||
readonly backoffCapMillis: number; | ||
readonly backoffMultiplier: number; | ||
private nextBackoffMillis; | ||
constructor(initialBackoffMillis: number, backoffCapMillis: number, backoffMultiplier: number); | ||
getBackoff(): number; | ||
reset(): void; | ||
} | ||
/** | ||
* Polls profiler server for instructions on behalf of a task and | ||
@@ -49,2 +62,3 @@ * collects and uploads profiles as requested | ||
private profileTypes; | ||
private retryer; | ||
timeProfiler: TimeProfiler | undefined; | ||
@@ -75,7 +89,2 @@ heapProfiler: HeapProfiler | undefined; | ||
* collecting another profile. | ||
* | ||
* TODO: implement backoff and retry. When error encountered in | ||
* createProfile() should be retried when response indicates this request | ||
* should be retried or with exponential backoff (up to one hour) if the | ||
* response does not indicate when to retry this request. | ||
*/ | ||
@@ -105,3 +114,3 @@ collectProfile(): Promise<number>; | ||
* If any problem is encountered, like a problem collecting or uploading the | ||
* profile, an error will be thrown. | ||
* profile, a message will be logged, and the error will otherwise be ignored. | ||
* | ||
@@ -108,0 +117,0 @@ * Public to allow for testing. |
@@ -64,2 +64,3 @@ "use strict"; | ||
var pify = require("pify"); | ||
var msToStr = require("pretty-ms"); | ||
var zlib = require("zlib"); | ||
@@ -81,3 +82,3 @@ var profile_1 = require("../../proto/profile"); | ||
/** | ||
* @return true if http status code indicates an error and false otherwise. | ||
* @return true iff http status code indicates an error. | ||
*/ | ||
@@ -88,2 +89,24 @@ function isErrorResponseStatusCode(code) { | ||
/** | ||
* @return number indicated by backoff if the response indicates a backoff and | ||
* that backoff is greater than 0. Otherwise returns undefined. | ||
*/ | ||
function getServerResponseBackoff(response) { | ||
// tslint:disable-next-line: no-any | ||
var body = response.body; | ||
if (body && body.error && body.error.details && | ||
Array.isArray(body.error.details)) { | ||
for (var _i = 0, _a = body.error.details; _i < _a.length; _i++) { | ||
var item = _a[_i]; | ||
if (typeof item === 'object' && item.retryDelay && | ||
typeof item.retryDelay === 'string') { | ||
var backoffMillis = parseDuration(item.retryDelay); | ||
if (backoffMillis > 0) { | ||
return backoffMillis; | ||
} | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
/** | ||
* @return true if an deployment is a Deployment and false otherwise. | ||
@@ -111,9 +134,2 @@ */ | ||
/** | ||
* @return true if response has statusCode. | ||
*/ | ||
// tslint:disable-next-line: no-any | ||
function hasHttpStatusCode(response) { | ||
return response && typeof response.statusCode === 'number'; | ||
} | ||
/** | ||
* Converts a profile to a compressed, base64 encoded string. | ||
@@ -143,2 +159,64 @@ * | ||
/** | ||
* Error constructed from HTTP server response which indicates backoff. | ||
*/ | ||
var BackoffResponseError = /** @class */ (function (_super) { | ||
__extends(BackoffResponseError, _super); | ||
function BackoffResponseError(response, backoffMillis) { | ||
var _this = _super.call(this, response.statusMessage) || this; | ||
_this.backoffMillis = backoffMillis; | ||
return _this; | ||
} | ||
return BackoffResponseError; | ||
}(Error)); | ||
/** | ||
* @return true if error is a BackoffResponseError and false otherwise | ||
*/ | ||
function isBackoffResponseError(err) { | ||
return typeof err.backoffMillis === 'number'; | ||
} | ||
/** | ||
* Class which tracks how long to wait before the next retry and can be | ||
* used to get this backoff. | ||
*/ | ||
var Retryer = /** @class */ (function () { | ||
function Retryer(initialBackoffMillis, backoffCapMillis, backoffMultiplier) { | ||
this.initialBackoffMillis = initialBackoffMillis; | ||
this.backoffCapMillis = backoffCapMillis; | ||
this.backoffMultiplier = backoffMultiplier; | ||
this.nextBackoffMillis = this.initialBackoffMillis; | ||
} | ||
Retryer.prototype.getBackoff = function () { | ||
var curBackoff = Math.random() * this.nextBackoffMillis; | ||
this.nextBackoffMillis = Math.min(this.backoffMultiplier * this.nextBackoffMillis, this.backoffCapMillis); | ||
return curBackoff; | ||
}; | ||
Retryer.prototype.reset = function () { | ||
this.nextBackoffMillis = this.initialBackoffMillis; | ||
}; | ||
return Retryer; | ||
}()); | ||
exports.Retryer = Retryer; | ||
/** | ||
* @return profile iff response indicates success and the returned profile was | ||
* valid. | ||
* @throws error when the response indicated failure or the returned profile | ||
* was not valid. | ||
*/ | ||
function responseToProfileOrError(err, body, response) { | ||
if (response && isErrorResponseStatusCode(response.statusCode)) { | ||
var delayMillis = getServerResponseBackoff(response); | ||
if (delayMillis) { | ||
throw new BackoffResponseError(response, delayMillis); | ||
} | ||
throw new Error(response.statusMessage); | ||
} | ||
if (err) { | ||
throw err; | ||
} | ||
if (isRequestProfile(body)) { | ||
return body; | ||
} | ||
throw new Error("Profile not valid: " + JSON.stringify(body) + "."); | ||
} | ||
/** | ||
* Polls profiler server for instructions on behalf of a task and | ||
@@ -188,2 +266,3 @@ * collects and uploads profiles as requested | ||
} | ||
_this.retryer = new Retryer(_this.config.initialBackoffMillis, _this.config.backoffCapMillis, _this.config.backoffMultiplier); | ||
return _this; | ||
@@ -216,3 +295,2 @@ } | ||
delayMillis = _a.sent(); | ||
// Schedule the next profile. | ||
setTimeout(this.runLoop.bind(this), delayMillis).unref(); | ||
@@ -230,11 +308,6 @@ return [2 /*return*/]; | ||
* collecting another profile. | ||
* | ||
* TODO: implement backoff and retry. When error encountered in | ||
* createProfile() should be retried when response indicates this request | ||
* should be retried or with exponential backoff (up to one hour) if the | ||
* response does not indicate when to retry this request. | ||
*/ | ||
Profiler.prototype.collectProfile = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var prof, err_1, err_2; | ||
var prof, err_1, backoff; | ||
return __generator(this, function (_a) { | ||
@@ -250,15 +323,15 @@ switch (_a.label) { | ||
err_1 = _a.sent(); | ||
this.logger.error("Failed to create profile: " + err_1); | ||
return [2 /*return*/, this.config.backoffMillis]; | ||
if (isBackoffResponseError(err_1)) { | ||
this.logger.debug("Must wait " + msToStr(err_1.backoffMillis) + " to create profile: " + err_1); | ||
return [2 /*return*/, Math.min(err_1.backoffMillis, this.config.serverBackoffCapMillis)]; | ||
} | ||
backoff = this.retryer.getBackoff(); | ||
this.logger.warn("Failed to create profile, waiting " + msToStr(backoff) + " to try again: " + err_1); | ||
return [2 /*return*/, backoff]; | ||
case 3: | ||
_a.trys.push([3, 5, , 6]); | ||
this.retryer.reset(); | ||
return [4 /*yield*/, this.profileAndUpload(prof)]; | ||
case 4: | ||
_a.sent(); | ||
return [3 /*break*/, 6]; | ||
case 5: | ||
err_2 = _a.sent(); | ||
this.logger.error("Failed to collect and upload profile: " + err_2); | ||
return [3 /*break*/, 6]; | ||
case 6: return [2 /*return*/, 0]; | ||
return [2 /*return*/, 0]; | ||
} | ||
@@ -288,41 +361,32 @@ }); | ||
return __awaiter(this, void 0, void 0, function () { | ||
var reqBody, options, _a, prof, response, message; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
reqBody = { | ||
deployment: this.deployment, | ||
profileType: this.profileTypes, | ||
}; | ||
options = { | ||
method: 'POST', | ||
uri: '/profiles', | ||
body: reqBody, | ||
json: true, | ||
// Default timeout for for a request is 1 minute, but request to create | ||
// profile is designed to hang until it is time to collect a profile | ||
// (up to one hour). | ||
timeout: parseDuration('1h'), | ||
}; | ||
this.logger.debug("Attempting to create profile."); | ||
return [4 /*yield*/, this.request(options)]; | ||
case 1: | ||
_a = _b.sent(), prof = _a[0], response = _a[1]; | ||
if (!hasHttpStatusCode(response)) { | ||
throw new Error('Server response missing status information.'); | ||
} | ||
if (isErrorResponseStatusCode(response.statusCode)) { | ||
message = response.statusCode; | ||
// tslint:disable-next-line: no-any | ||
if (response.statusMessage) { | ||
message = response.statusMessage; | ||
var _this = this; | ||
var reqBody, options; | ||
return __generator(this, function (_a) { | ||
reqBody = { | ||
deployment: this.deployment, | ||
profileType: this.profileTypes, | ||
}; | ||
options = { | ||
method: 'POST', | ||
uri: '/profiles', | ||
body: reqBody, | ||
json: true, | ||
// Default timeout for for a request is 1 minute, but request to create | ||
// profile is designed to hang until it is time to collect a profile | ||
// (up to one hour). | ||
timeout: parseDuration('1h'), | ||
}; | ||
this.logger.debug("Attempting to create profile."); | ||
return [2 /*return*/, new Promise(function (resolve, reject) { | ||
_this.request(options, function (err, body, response) { | ||
try { | ||
var prof = responseToProfileOrError(err, body, response); | ||
_this.logger.debug("Successfully created profile " + prof.profileType + "."); | ||
resolve(prof); | ||
} | ||
throw new Error(message.toString()); | ||
} | ||
if (!isRequestProfile(prof)) { | ||
throw new Error("Profile not valid: " + JSON.stringify(prof) + "."); | ||
} | ||
this.logger.debug("Successfully created profile " + prof.profileType + "."); | ||
return [2 /*return*/, prof]; | ||
} | ||
catch (err) { | ||
reject(err); | ||
} | ||
}); | ||
})]; | ||
}); | ||
@@ -334,3 +398,3 @@ }); | ||
* If any problem is encountered, like a problem collecting or uploading the | ||
* profile, an error will be thrown. | ||
* profile, a message will be logged, and the error will otherwise be ignored. | ||
* | ||
@@ -341,6 +405,8 @@ * Public to allow for testing. | ||
return __awaiter(this, void 0, void 0, function () { | ||
var options, _a, body, response, message; | ||
var err_2, options, _a, body, serverResponse, response, message, err_3; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: return [4 /*yield*/, this.profile(prof)]; | ||
case 0: | ||
_b.trys.push([0, 2, , 3]); | ||
return [4 /*yield*/, this.profile(prof)]; | ||
case 1: | ||
@@ -350,2 +416,8 @@ prof = _b.sent(); | ||
prof.labels = this.profileLabels; | ||
return [3 /*break*/, 3]; | ||
case 2: | ||
err_2 = _b.sent(); | ||
this.logger.debug("Failed to collect profile: " + err_2); | ||
return [2 /*return*/]; | ||
case 3: | ||
options = { | ||
@@ -357,18 +429,24 @@ method: 'PATCH', | ||
}; | ||
_b.label = 4; | ||
case 4: | ||
_b.trys.push([4, 6, , 7]); | ||
return [4 /*yield*/, this.request(options)]; | ||
case 2: | ||
_a = _b.sent(), body = _a[0], response = _a[1]; | ||
if (!hasHttpStatusCode(response)) { | ||
throw new Error('Server response missing status information when attempting to upload profile.'); | ||
} | ||
case 5: | ||
_a = _b.sent(), body = _a[0], serverResponse = _a[1]; | ||
response = serverResponse; | ||
if (isErrorResponseStatusCode(response.statusCode)) { | ||
message = response.statusCode; | ||
// tslint:disable-next-line: no-any | ||
if (response.statusMessage) { | ||
message = response.statusMessage; | ||
} | ||
throw new Error("Could not upload profile: " + message + "."); | ||
this.logger.debug("Could not upload profile: " + message + "."); | ||
return [2 /*return*/]; | ||
} | ||
this.logger.debug("Successfully uploaded profile " + prof.profileType + "."); | ||
return [2 /*return*/]; | ||
return [3 /*break*/, 7]; | ||
case 6: | ||
err_3 = _b.sent(); | ||
this.logger.debug("Failed to upload profile: " + err_3); | ||
return [3 /*break*/, 7]; | ||
case 7: return [2 /*return*/]; | ||
} | ||
@@ -375,0 +453,0 @@ }); |
{ | ||
"name": "@google-cloud/profiler", | ||
"version": "0.1.6", | ||
"version": "0.1.7", | ||
"description": "Adds support for Google Cloud Profiler to node.js applications", | ||
@@ -10,3 +10,3 @@ "repository": "GoogleCloudPlatform/cloud-profiler-nodejs", | ||
"install": "node-gyp rebuild", | ||
"test": "nyc mocha out/test/test-*.js", | ||
"test": "nyc mocha out/test/test-*.js && nyc --no-clean mocha out/system-test/test-*.js --timeout=60000", | ||
"check": "gts check", | ||
@@ -37,2 +37,3 @@ "clean": "gts clean", | ||
"pify": "^3.0.0", | ||
"pretty-ms": "^3.1.0", | ||
"protobufjs": "~6.8.0", | ||
@@ -47,5 +48,6 @@ "request": "^2.83.0", | ||
"@types/mocha": "^2.2.43", | ||
"@types/nock": "^8.2.1", | ||
"@types/nock": "^9.1.0", | ||
"@types/node": "^8.0.30", | ||
"@types/pify": "^3.0.0", | ||
"@types/pretty-ms": "^3.0.0", | ||
"@types/request": "^2.0.7", | ||
@@ -75,5 +77,6 @@ "@types/sinon": "^4.0.0", | ||
"proto", | ||
"out/test" | ||
"out/test", | ||
"out/system-test" | ||
] | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1172317
22508
12
19
+ Addedpretty-ms@^3.1.0
+ Addedparse-ms@1.0.1(transitive)
+ Addedpretty-ms@3.2.0(transitive)
+ Addedpsl@1.10.0(transitive)
- Removedpsl@1.9.0(transitive)