@google-cloud/profiler
Advanced tools
Comparing version 0.1.14 to 0.2.0
@@ -16,4 +16,4 @@ /** | ||
*/ | ||
import { AuthenticationConfig } from '../third_party/types/common-types'; | ||
export interface Config extends AuthenticationConfig { | ||
import { GoogleAuthOptions } from '@google-cloud/common'; | ||
export interface Config extends GoogleAuthOptions { | ||
projectId?: string; | ||
@@ -37,4 +37,7 @@ logLevel?: number; | ||
baseApiUrl?: string; | ||
localProfilingPeriodMillis?: number; | ||
localLogPeriodMillis?: number; | ||
localTimeDurationMillis?: number; | ||
} | ||
export interface ProfilerConfig extends AuthenticationConfig { | ||
export interface ProfilerConfig extends GoogleAuthOptions { | ||
projectId?: string; | ||
@@ -58,2 +61,5 @@ logLevel: number; | ||
baseApiUrl: string; | ||
localProfilingPeriodMillis: number; | ||
localLogPeriodMillis: number; | ||
localTimeDurationMillis: number; | ||
} | ||
@@ -73,2 +79,5 @@ export declare const defaultConfig: { | ||
serverBackoffCapMillis: number; | ||
localProfilingPeriodMillis: number; | ||
localLogPeriodMillis: number; | ||
localTimeDurationMillis: number; | ||
}; |
@@ -18,5 +18,4 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var parseDuration = require('parse-duration'); | ||
var common = require('@google-cloud/common'); | ||
var extend = require('extend'); | ||
const parseDuration = require('parse-duration'); | ||
const extend = require('extend'); | ||
// Default values for configuration for a profiler. | ||
@@ -31,3 +30,3 @@ exports.defaultConfig = { | ||
heapMaxStackDepth: 64, | ||
initialBackoffMillis: 1000, | ||
initialBackoffMillis: 60 * 1000, | ||
backoffCapMillis: parseDuration('1h'), | ||
@@ -39,4 +38,7 @@ backoffMultiplier: 1.3, | ||
// https://nodejs.org/dist/latest-v9.x/docs/api/timers.html#timers_settimeout_callback_delay_args. | ||
serverBackoffCapMillis: 2147483647 | ||
serverBackoffCapMillis: 2147483647, | ||
localProfilingPeriodMillis: 1000, | ||
localLogPeriodMillis: 10000, | ||
localTimeDurationMillis: 1000 | ||
}; | ||
//# sourceMappingURL=config.js.map |
@@ -1,11 +0,33 @@ | ||
import { Config, ProfilerConfig } from './config'; | ||
/** | ||
* Sets unset values in the configuration to the value retrieved from | ||
* environment variables, metadata, or specified in defaultConfig. | ||
* Throws error if value that must be set cannot be initialized. | ||
* Copyright 2017 Google Inc. All Rights Reserved. | ||
* | ||
* Exported for testing purposes. | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
export declare function initConfig(config: Config): Promise<ProfilerConfig>; | ||
import { SemVer } from 'semver'; | ||
import { Config } from './config'; | ||
import { Profiler } from './profiler'; | ||
/** | ||
* Returns true if the version passed in satifised version requirements | ||
* specified in the profiler's package.json. | ||
* | ||
* Exported for testing. | ||
*/ | ||
export declare function nodeVersionOkay(version: string | SemVer): boolean; | ||
/** | ||
* Initializes the config, and starts heap profiler if the heap profiler is | ||
* needed. Returns a profiler if creation is successful. Otherwise, returns | ||
* rejected promise. | ||
*/ | ||
export declare function createProfiler(config: Config): Promise<Profiler>; | ||
/** | ||
* Starts the profiling agent and returns a promise. | ||
@@ -12,0 +34,0 @@ * If any error is encountered when configuring the profiler the promise will |
@@ -25,39 +25,13 @@ "use strict"; | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [0, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var delay = require("delay"); | ||
var extend = require("extend"); | ||
var gcpMetadata = require("gcp-metadata"); | ||
var path = require("path"); | ||
var semver = require("semver"); | ||
var config_1 = require("./config"); | ||
var profiler_1 = require("./profiler"); | ||
var common = require('@google-cloud/common'); | ||
var pjson = require('../../package.json'); | ||
const common_1 = require("@google-cloud/common"); | ||
const delay = require("delay"); | ||
const extend = require("extend"); | ||
const fs = require("fs"); | ||
const gcpMetadata = require("gcp-metadata"); | ||
const semver = require("semver"); | ||
const config_1 = require("./config"); | ||
const profiler_1 = require("./profiler"); | ||
const heapProfiler = require("./profilers/heap-profiler"); | ||
const pjson = require('../../package.json'); | ||
/** | ||
@@ -68,12 +42,5 @@ * @return value of metadata field. | ||
function getMetadataInstanceField(field) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var res; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, gcpMetadata.instance(field)]; | ||
case 1: | ||
res = _a.sent(); | ||
return [2 /*return*/, res.data]; | ||
} | ||
}); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const res = yield gcpMetadata.instance(field); | ||
return res.data; | ||
}); | ||
@@ -87,63 +54,107 @@ } | ||
* Sets unset values in the configuration to the value retrieved from | ||
* environment variables, metadata, or specified in defaultConfig. | ||
* environment variables or specified in defaultConfig. | ||
* Throws error if value that must be set cannot be initialized. | ||
* | ||
* Exported for testing purposes. | ||
*/ | ||
function initConfig(config) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var envConfig, envLogLevel, envSetConfig, val, mergedConfig, _a, instance, zone; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
config = common.util.normalizeArguments(null, config); | ||
envConfig = { | ||
projectId: process.env.GCLOUD_PROJECT, | ||
serviceContext: { | ||
service: process.env.GAE_SERVICE, | ||
version: process.env.GAE_VERSION, | ||
} | ||
}; | ||
if (process.env.GCLOUD_PROFILER_LOGLEVEL !== undefined) { | ||
envLogLevel = Number(process.env.GCLOUD_PROFILER_LOGLEVEL); | ||
if (!isNaN(envLogLevel)) { | ||
envConfig.logLevel = envLogLevel; | ||
} | ||
} | ||
envSetConfig = {}; | ||
val = process.env.GCLOUD_PROFILER_CONFIG; | ||
if (val) { | ||
envSetConfig = require(path.resolve(val)); | ||
} | ||
mergedConfig = extend(true, {}, config_1.defaultConfig, envSetConfig, envConfig, config); | ||
if (!(!mergedConfig.zone || !mergedConfig.instance)) return [3 /*break*/, 2]; | ||
return [4 /*yield*/, Promise | ||
.all([ | ||
getMetadataInstanceField('name'), getMetadataInstanceField('zone') | ||
]) | ||
.catch(function (err) { | ||
// ignore errors, which will occur when not on GCE. | ||
})]; | ||
case 1: | ||
_a = (_b.sent()) || | ||
[undefined, undefined], instance = _a[0], zone = _a[1]; | ||
if (!mergedConfig.zone && zone) { | ||
mergedConfig.zone = zone.substring(zone.lastIndexOf('/') + 1); | ||
} | ||
if (!mergedConfig.instance && instance) { | ||
mergedConfig.instance = instance; | ||
} | ||
_b.label = 2; | ||
case 2: | ||
if (!hasService(mergedConfig)) { | ||
throw new Error('Service must be specified in the configuration.'); | ||
} | ||
return [2 /*return*/, mergedConfig]; | ||
function initConfigLocal(config) { | ||
config = common_1.util.normalizeArguments(null, config); | ||
const envConfig = { | ||
projectId: process.env.GCLOUD_PROJECT, | ||
serviceContext: { | ||
service: process.env.GAE_SERVICE, | ||
version: process.env.GAE_VERSION, | ||
} | ||
}; | ||
if (process.env.GCLOUD_PROFILER_LOGLEVEL !== undefined) { | ||
const envLogLevel = Number(process.env.GCLOUD_PROFILER_LOGLEVEL); | ||
if (!isNaN(envLogLevel)) { | ||
envConfig.logLevel = envLogLevel; | ||
} | ||
} | ||
let envSetConfig = {}; | ||
const configPath = process.env.GCLOUD_PROFILER_CONFIG; | ||
if (configPath) { | ||
let envSetConfigBuf; | ||
try { | ||
envSetConfigBuf = fs.readFileSync(configPath); | ||
} | ||
catch (e) { | ||
throw Error(`Could not read GCLOUD_PROFILER_CONFIG ${configPath}: ${e}`); | ||
} | ||
try { | ||
envSetConfig = JSON.parse(envSetConfigBuf.toString()); | ||
} | ||
catch (e) { | ||
throw Error(`Could not parse GCLOUD_PROFILER_CONFIG ${configPath}: ${e}`); | ||
} | ||
} | ||
const mergedConfig = extend(true, {}, config_1.defaultConfig, envSetConfig, envConfig, config); | ||
if (!hasService(mergedConfig)) { | ||
throw new Error('Service must be specified in the configuration.'); | ||
} | ||
return mergedConfig; | ||
} | ||
/** | ||
* Sets unset values in the configuration which can be retrieved from GCP | ||
* metadata. | ||
*/ | ||
function initConfigMetadata(config) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!config.zone || !config.instance) { | ||
const [instance, zone] = (yield Promise | ||
.all([ | ||
getMetadataInstanceField('name'), getMetadataInstanceField('zone') | ||
]) | ||
.catch((err) => { | ||
// ignore errors, which will occur when not on GCE. | ||
})) || | ||
[undefined, undefined]; | ||
if (!config.zone && zone) { | ||
config.zone = zone.substring(zone.lastIndexOf('/') + 1); | ||
} | ||
}); | ||
if (!config.instance && instance) { | ||
config.instance = instance; | ||
} | ||
} | ||
return config; | ||
}); | ||
} | ||
exports.initConfig = initConfig; | ||
var profiler = undefined; | ||
/** | ||
* Returns true if the version passed in satifised version requirements | ||
* specified in the profiler's package.json. | ||
* | ||
* Exported for testing. | ||
*/ | ||
function nodeVersionOkay(version) { | ||
// Coerce version if possible, to remove any pre-release, alpha, beta, etc | ||
// tags. | ||
version = semver.coerce(version) || version; | ||
return semver.satisfies(version, pjson.engines.node); | ||
} | ||
exports.nodeVersionOkay = nodeVersionOkay; | ||
/** | ||
* Initializes the config, and starts heap profiler if the heap profiler is | ||
* needed. Returns a profiler if creation is successful. Otherwise, returns | ||
* rejected promise. | ||
*/ | ||
function createProfiler(config) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!nodeVersionOkay(process.version)) { | ||
throw new Error(`Could not start profiler: node version ${process.version}` + | ||
` does not satisfies "${pjson.engines.node}"` + | ||
'\nSee https://github.com/GoogleCloudPlatform/cloud-profiler-nodejs#prerequisites' + | ||
' for details.'); | ||
} | ||
let profilerConfig = initConfigLocal(config); | ||
// Start the heap profiler if profiler config does not indicate heap profiling | ||
// is disabled. This must be done before any asynchronous calls are made so | ||
// all memory allocations made after start() is called can be captured. | ||
if (!profilerConfig.disableHeap) { | ||
heapProfiler.start(profilerConfig.heapIntervalBytes, profilerConfig.heapMaxStackDepth); | ||
} | ||
profilerConfig = yield initConfigMetadata(profilerConfig); | ||
return new profiler_1.Profiler(profilerConfig); | ||
}); | ||
} | ||
exports.createProfiler = createProfiler; | ||
/** | ||
* Starts the profiling agent and returns a promise. | ||
@@ -162,31 +173,13 @@ * If any error is encountered when configuring the profiler the promise will | ||
*/ | ||
function start(config) { | ||
if (config === void 0) { config = {}; } | ||
return __awaiter(this, void 0, void 0, function () { | ||
var normalizedConfig, e_1; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
if (!semver.satisfies(process.version, pjson.engines.node)) { | ||
logError("Could not start profiler: node version " + process.version + | ||
(" does not satisfies \"" + pjson.engines.node + "\""), config); | ||
return [2 /*return*/]; | ||
} | ||
_a.label = 1; | ||
case 1: | ||
_a.trys.push([1, 3, , 4]); | ||
return [4 /*yield*/, initConfig(config)]; | ||
case 2: | ||
normalizedConfig = _a.sent(); | ||
return [3 /*break*/, 4]; | ||
case 3: | ||
e_1 = _a.sent(); | ||
logError("Could not start profiler: " + e_1, config); | ||
return [2 /*return*/]; | ||
case 4: | ||
profiler = new profiler_1.Profiler(normalizedConfig); | ||
profiler.start(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
function start(config = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let profiler; | ||
try { | ||
profiler = yield createProfiler(config); | ||
} | ||
catch (e) { | ||
logError(`${e}`, config); | ||
return; | ||
} | ||
profiler.start(); | ||
}); | ||
@@ -196,3 +189,3 @@ } | ||
function logError(msg, config) { | ||
var logger = new common.logger({ level: common.logger.LEVELS[config.logLevel || 2], tag: pjson.name }); | ||
const logger = new common_1.Logger({ level: common_1.Logger.LEVELS[config.logLevel || 2], tag: pjson.name }); | ||
logger.error(msg); | ||
@@ -204,37 +197,41 @@ } | ||
*/ | ||
function startLocal(config) { | ||
if (config === void 0) { config = {}; } | ||
return __awaiter(this, void 0, void 0, function () { | ||
var normalizedConfig, heap, wall; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, initConfig(config)]; | ||
case 1: | ||
normalizedConfig = _a.sent(); | ||
profiler = new profiler_1.Profiler(normalizedConfig); | ||
_a.label = 2; | ||
case 2: | ||
if (!true) return [3 /*break*/, 8]; | ||
if (!!config.disableHeap) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, profiler.profile({ name: 'HEAP-Profile' + new Date(), profileType: 'HEAP' })]; | ||
case 3: | ||
heap = _a.sent(); | ||
_a.label = 4; | ||
case 4: | ||
if (!!config.disableTime) return [3 /*break*/, 6]; | ||
return [4 /*yield*/, profiler.profile({ | ||
name: 'Time-Profile' + new Date(), | ||
profileType: 'WALL', | ||
duration: '10s' | ||
})]; | ||
case 5: | ||
wall = _a.sent(); | ||
_a.label = 6; | ||
case 6: return [4 /*yield*/, delay(1000)]; | ||
case 7: | ||
_a.sent(); | ||
return [3 /*break*/, 2]; | ||
case 8: return [2 /*return*/]; | ||
function startLocal(config = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let profiler; | ||
try { | ||
profiler = yield createProfiler(config); | ||
} | ||
catch (e) { | ||
logError(`${e}`, config); | ||
return; | ||
} | ||
// Set up periodic logging. | ||
const logger = new common_1.Logger({ level: common_1.Logger.LEVELS[profiler.config.logLevel], tag: pjson.name }); | ||
let heapProfileCount = 0; | ||
let timeProfileCount = 0; | ||
let prevLogTime = Date.now(); | ||
setInterval(() => { | ||
const curTime = Date.now(); | ||
const { rss, heapTotal, heapUsed } = process.memoryUsage(); | ||
logger.debug(new Date().toISOString(), 'rss', (rss / (1024 * 1024)).toFixed(3), 'MiB,', 'heap total', (heapTotal / (1024 * 1024)).toFixed(3), 'MiB,', 'heap used', (heapUsed / (1024 * 1024)).toFixed(3), 'MiB,', 'heap profile collection rate', (heapProfileCount * 1000 / (curTime - prevLogTime)).toFixed(3), 'profiles/s,', 'time profile collection rate', (timeProfileCount * 1000 / (curTime - prevLogTime)).toFixed(3), 'profiles/s'); | ||
heapProfileCount = 0; | ||
timeProfileCount = 0; | ||
prevLogTime = curTime; | ||
}, profiler.config.localLogPeriodMillis); | ||
// Periodic profiling | ||
setInterval(() => __awaiter(this, void 0, void 0, function* () { | ||
if (!config.disableHeap) { | ||
const heap = yield profiler.profile({ name: 'Heap-Profile' + new Date(), profileType: 'HEAP' }); | ||
heapProfileCount++; | ||
} | ||
}); | ||
yield delay(profiler.config.localProfilingPeriodMillis / 2); | ||
if (!config.disableTime) { | ||
const wall = yield profiler.profile({ | ||
name: 'Time-Profile' + new Date(), | ||
profileType: 'WALL', | ||
duration: profiler.config.localTimeDurationMillis.toString() + 'ms' | ||
}); | ||
timeProfileCount++; | ||
} | ||
}), profiler.config.localProfilingPeriodMillis); | ||
}); | ||
@@ -241,0 +238,0 @@ } |
@@ -1,6 +0,19 @@ | ||
import { Common } from '../third_party/types/common-types'; | ||
/** | ||
* Copyright 2017 Google Inc. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
import { ServiceObject } from '@google-cloud/common'; | ||
import { ProfilerConfig } from './config'; | ||
import { HeapProfiler } from './profilers/heap-profiler'; | ||
import { TimeProfiler } from './profilers/time-profiler'; | ||
export declare const common: Common; | ||
/** | ||
@@ -19,2 +32,3 @@ * Interface for deployment field of RequestProfile. Profiles with matching | ||
version?: string; | ||
language: string; | ||
}; | ||
@@ -40,2 +54,9 @@ } | ||
/** | ||
* @return if the backoff duration can be parsed, then the backoff duration in | ||
* ms, otherwise undefined. | ||
* | ||
* Public for testing. | ||
*/ | ||
export declare function parseBackoffDuration(backoffMessage: string): number | undefined; | ||
/** | ||
* Class which tracks how long to wait before the next retry and can be | ||
@@ -56,6 +77,8 @@ * used to get this backoff. | ||
* Polls profiler server for instructions on behalf of a task and | ||
* collects and uploads profiles as requested | ||
* collects and uploads profiles as requested. | ||
* | ||
* If heap profiling is enabled, the heap profiler must be enabled before heap | ||
* profiles can be collected. | ||
*/ | ||
export declare class Profiler extends common.ServiceObject { | ||
private config; | ||
export declare class Profiler extends ServiceObject { | ||
private logger; | ||
@@ -67,3 +90,3 @@ private profileLabels; | ||
timeProfiler: TimeProfiler | undefined; | ||
heapProfiler: HeapProfiler | undefined; | ||
config: ProfilerConfig; | ||
constructor(config: ProfilerConfig); | ||
@@ -70,0 +93,0 @@ /** |
@@ -17,12 +17,2 @@ "use strict"; | ||
*/ | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
@@ -36,41 +26,14 @@ return new (P || (P = Promise))(function (resolve, reject) { | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [0, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var pify = require("pify"); | ||
var msToStr = require("pretty-ms"); | ||
var zlib = require("zlib"); | ||
var profile_1 = require("../../proto/profile"); | ||
var heap_profiler_1 = require("./profilers/heap-profiler"); | ||
var time_profiler_1 = require("./profilers/time-profiler"); | ||
exports.common = require('@google-cloud/common'); | ||
var parseDuration = require('parse-duration'); | ||
var pjson = require('../../package.json'); | ||
var SCOPE = 'https://www.googleapis.com/auth/monitoring.write'; | ||
var gzip = pify(zlib.gzip); | ||
const common_1 = require("@google-cloud/common"); | ||
const pify = require("pify"); | ||
const msToStr = require("pretty-ms"); | ||
const zlib = require("zlib"); | ||
const profile_1 = require("../../proto/profile"); | ||
const heapProfiler = require("./profilers/heap-profiler"); | ||
const time_profiler_1 = require("./profilers/time-profiler"); | ||
const parseDuration = require('parse-duration'); | ||
const pjson = require('../../package.json'); | ||
const SCOPE = 'https://www.googleapis.com/auth/monitoring.write'; | ||
const gzip = pify(zlib.gzip); | ||
var ProfileTypes; | ||
@@ -93,14 +56,26 @@ (function (ProfileTypes) { | ||
// 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; | ||
} | ||
} | ||
const body = response.body; | ||
// The response currently does not have field containing the server-specified | ||
// backoff. As a workaround, response body's message is parsed to get the | ||
// backoff. | ||
// TODO (issue #250): Remove this workaround and get the retry delay from | ||
// body.error.details. | ||
if (body && body.message && typeof body.message === 'string') { | ||
return parseBackoffDuration(body.message); | ||
} | ||
return undefined; | ||
} | ||
/** | ||
* @return if the backoff duration can be parsed, then the backoff duration in | ||
* ms, otherwise undefined. | ||
* | ||
* Public for testing. | ||
*/ | ||
function parseBackoffDuration(backoffMessage) { | ||
const backoffMessageRegex = /action throttled, backoff for ((?:([0-9]+)h)?(?:([0-9]+)m)?([0-9.]+)s)$/; | ||
const [, duration] = backoffMessageRegex.exec(backoffMessage) || [undefined, undefined]; | ||
if (duration) { | ||
const backoffMillis = parseDuration(duration); | ||
if (backoffMillis > 0) { | ||
return backoffMillis; | ||
} | ||
@@ -110,2 +85,3 @@ } | ||
} | ||
exports.parseBackoffDuration = parseBackoffDuration; | ||
/** | ||
@@ -119,3 +95,6 @@ * @return true if an deployment is a Deployment and false otherwise. | ||
(deployment.target === undefined || | ||
typeof deployment.target === 'string'); | ||
typeof deployment.target === 'string') && | ||
(deployment.labels !== undefined && | ||
deployment.labels.language !== undefined && | ||
typeof deployment.labels.language === 'string'); | ||
} | ||
@@ -144,14 +123,6 @@ /** | ||
function profileBytes(p) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var buffer, gzBuf; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
buffer = profile_1.perftools.profiles.Profile.encode(p).finish(); | ||
return [4 /*yield*/, gzip(buffer)]; | ||
case 1: | ||
gzBuf = _a.sent(); | ||
return [2 /*return*/, gzBuf.toString('base64')]; | ||
} | ||
}); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const buffer = profile_1.perftools.profiles.Profile.encode(p).finish(); | ||
const gzBuf = yield gzip(buffer); | ||
return gzBuf.toString('base64'); | ||
}); | ||
@@ -162,11 +133,8 @@ } | ||
*/ | ||
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; | ||
class BackoffResponseError extends Error { | ||
constructor(response, backoffMillis) { | ||
super(response.statusMessage); | ||
this.backoffMillis = backoffMillis; | ||
} | ||
return BackoffResponseError; | ||
}(Error)); | ||
} | ||
/** | ||
@@ -182,5 +150,4 @@ * @return true if error is a BackoffResponseError and false otherwise | ||
*/ | ||
var Retryer = /** @class */ (function () { | ||
function Retryer(initialBackoffMillis, backoffCapMillis, backoffMultiplier, random) { | ||
if (random === void 0) { random = Math.random; } | ||
class Retryer { | ||
constructor(initialBackoffMillis, backoffCapMillis, backoffMultiplier, random = Math.random) { | ||
this.initialBackoffMillis = initialBackoffMillis; | ||
@@ -192,12 +159,11 @@ this.backoffCapMillis = backoffCapMillis; | ||
} | ||
Retryer.prototype.getBackoff = function () { | ||
var curBackoff = this.random() * this.nextBackoffMillis; | ||
getBackoff() { | ||
const curBackoff = this.random() * this.nextBackoffMillis; | ||
this.nextBackoffMillis = Math.min(this.backoffMultiplier * this.nextBackoffMillis, this.backoffCapMillis); | ||
return curBackoff; | ||
}; | ||
Retryer.prototype.reset = function () { | ||
} | ||
reset() { | ||
this.nextBackoffMillis = this.initialBackoffMillis; | ||
}; | ||
return Retryer; | ||
}()); | ||
} | ||
} | ||
exports.Retryer = Retryer; | ||
@@ -211,4 +177,5 @@ /** | ||
function responseToProfileOrError(err, body, response) { | ||
// response.statusCode is guaranteed to exist on client requests. | ||
if (response && isErrorResponseStatusCode(response.statusCode)) { | ||
var delayMillis = getServerResponseBackoff(response); | ||
const delayMillis = getServerResponseBackoff(response); | ||
if (delayMillis) { | ||
@@ -225,14 +192,15 @@ throw new BackoffResponseError(response, delayMillis); | ||
} | ||
throw new Error("Profile not valid: " + JSON.stringify(body) + "."); | ||
throw new Error(`Profile not valid: ${JSON.stringify(body)}.`); | ||
} | ||
/** | ||
* Polls profiler server for instructions on behalf of a task and | ||
* collects and uploads profiles as requested | ||
* collects and uploads profiles as requested. | ||
* | ||
* If heap profiling is enabled, the heap profiler must be enabled before heap | ||
* profiles can be collected. | ||
*/ | ||
var Profiler = /** @class */ (function (_super) { | ||
__extends(Profiler, _super); | ||
function Profiler(config) { | ||
var _this = this; | ||
config = exports.common.util.normalizeArguments(null, config); | ||
var serviceConfig = { | ||
class Profiler extends common_1.ServiceObject { | ||
constructor(config) { | ||
config = common_1.util.normalizeArguments(null, config); | ||
const serviceConfig = { | ||
baseUrl: config.baseApiUrl, | ||
@@ -242,35 +210,30 @@ scopes: [SCOPE], | ||
}; | ||
_this = _super.call(this, { parent: new exports.common.Service(serviceConfig, config), baseUrl: '/' }) || this; | ||
_this.config = config; | ||
_this.logger = new exports.common.logger({ | ||
level: exports.common.logger.LEVELS[config.logLevel], | ||
tag: pjson.name | ||
}); | ||
var labels = {}; | ||
if (_this.config.zone) { | ||
labels.zone = _this.config.zone; | ||
super({ parent: new common_1.Service(serviceConfig, config), baseUrl: '/' }); | ||
this.config = config; | ||
this.logger = new common_1.Logger({ level: common_1.Logger.LEVELS[config.logLevel], tag: pjson.name }); | ||
const labels = { language: 'nodejs' }; | ||
if (this.config.zone) { | ||
labels.zone = this.config.zone; | ||
} | ||
if (_this.config.serviceContext.version) { | ||
labels.version = _this.config.serviceContext.version; | ||
if (this.config.serviceContext.version) { | ||
labels.version = this.config.serviceContext.version; | ||
} | ||
_this.deployment = { | ||
projectId: _this.config.projectId, | ||
target: _this.config.serviceContext.service, | ||
labels: labels | ||
this.deployment = { | ||
projectId: this.config.projectId, | ||
target: this.config.serviceContext.service, | ||
labels | ||
}; | ||
_this.profileLabels = {}; | ||
if (_this.config.instance) { | ||
_this.profileLabels.instance = _this.config.instance; | ||
this.profileLabels = {}; | ||
if (this.config.instance) { | ||
this.profileLabels.instance = this.config.instance; | ||
} | ||
_this.profileTypes = []; | ||
if (!_this.config.disableTime) { | ||
_this.profileTypes.push(ProfileTypes.Wall); | ||
_this.timeProfiler = new time_profiler_1.TimeProfiler(_this.config.timeIntervalMicros); | ||
this.profileTypes = []; | ||
if (!this.config.disableTime) { | ||
this.profileTypes.push(ProfileTypes.Wall); | ||
this.timeProfiler = new time_profiler_1.TimeProfiler(this.config.timeIntervalMicros); | ||
} | ||
if (!_this.config.disableHeap) { | ||
_this.profileTypes.push(ProfileTypes.Heap); | ||
_this.heapProfiler = new heap_profiler_1.HeapProfiler(_this.config.heapIntervalBytes, _this.config.heapMaxStackDepth); | ||
if (!this.config.disableHeap) { | ||
this.profileTypes.push(ProfileTypes.Heap); | ||
} | ||
_this.retryer = new Retryer(_this.config.initialBackoffMillis, _this.config.backoffCapMillis, _this.config.backoffMultiplier); | ||
return _this; | ||
this.retryer = new Retryer(this.config.initialBackoffMillis, this.config.backoffCapMillis, this.config.backoffMultiplier); | ||
} | ||
@@ -287,5 +250,5 @@ /** | ||
*/ | ||
Profiler.prototype.start = function () { | ||
start() { | ||
this.runLoop(); | ||
}; | ||
} | ||
/** | ||
@@ -295,16 +258,8 @@ * Endlessly polls the profiler server for instructions, and collects and | ||
*/ | ||
Profiler.prototype.runLoop = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var delayMillis; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, this.collectProfile()]; | ||
case 1: | ||
delayMillis = _a.sent(); | ||
setTimeout(this.runLoop.bind(this), delayMillis).unref(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
runLoop() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const delayMillis = yield this.collectProfile(); | ||
setTimeout(this.runLoop.bind(this), delayMillis).unref(); | ||
}); | ||
}; | ||
} | ||
/** | ||
@@ -317,32 +272,22 @@ * Waits for profiler server to tell it to collect a profile, then collects | ||
*/ | ||
Profiler.prototype.collectProfile = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var prof, err_1, backoff; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
_a.trys.push([0, 2, , 3]); | ||
return [4 /*yield*/, this.createProfile()]; | ||
case 1: | ||
prof = _a.sent(); | ||
return [3 /*break*/, 3]; | ||
case 2: | ||
err_1 = _a.sent(); | ||
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: | ||
this.retryer.reset(); | ||
return [4 /*yield*/, this.profileAndUpload(prof)]; | ||
case 4: | ||
_a.sent(); | ||
return [2 /*return*/, 0]; | ||
collectProfile() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let prof; | ||
try { | ||
prof = yield this.createProfile(); | ||
} | ||
catch (err) { | ||
if (isBackoffResponseError(err)) { | ||
this.logger.debug(`Must wait ${msToStr(err.backoffMillis)} to create profile: ${err}`); | ||
return Math.min(err.backoffMillis, this.config.serverBackoffCapMillis); | ||
} | ||
}); | ||
const backoff = this.retryer.getBackoff(); | ||
this.logger.warn(`Failed to create profile, waiting ${msToStr(backoff)} to try again: ${err}`); | ||
return backoff; | ||
} | ||
this.retryer.reset(); | ||
yield this.profileAndUpload(prof); | ||
return 0; | ||
}); | ||
}; | ||
} | ||
/** | ||
@@ -366,37 +311,33 @@ * Talks to profiler server, which hangs until server indicates | ||
*/ | ||
Profiler.prototype.createProfile = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
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); | ||
} | ||
catch (err) { | ||
reject(err); | ||
} | ||
}); | ||
})]; | ||
createProfile() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const reqBody = { | ||
deployment: this.deployment, | ||
profileType: this.profileTypes, | ||
}; | ||
const 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 new Promise((resolve, reject) => { | ||
this.request(options, (err, body, response) => { | ||
try { | ||
const prof = responseToProfileOrError(err, body, response); | ||
this.logger.debug(`Successfully created profile ${prof.profileType}.`); | ||
resolve(prof); | ||
} | ||
catch (err) { | ||
reject(err); | ||
} | ||
}); | ||
}); | ||
}); | ||
}; | ||
} | ||
/** | ||
@@ -409,52 +350,36 @@ * Collects a profile of the type specified by the profileType field of prof. | ||
*/ | ||
Profiler.prototype.profileAndUpload = function (prof) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var err_2, options, _a, body, serverResponse, response, message, err_3; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
_b.trys.push([0, 2, , 3]); | ||
return [4 /*yield*/, this.profile(prof)]; | ||
case 1: | ||
prof = _b.sent(); | ||
this.logger.debug("Successfully collected profile " + prof.profileType + "."); | ||
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 = { | ||
method: 'PATCH', | ||
uri: this.config.baseApiUrl + '/' + prof.name, | ||
body: prof, | ||
json: true, | ||
}; | ||
_b.label = 4; | ||
case 4: | ||
_b.trys.push([4, 6, , 7]); | ||
return [4 /*yield*/, this.request(options)]; | ||
case 5: | ||
_a = _b.sent(), body = _a[0], serverResponse = _a[1]; | ||
response = serverResponse; | ||
if (isErrorResponseStatusCode(response.statusCode)) { | ||
message = response.statusCode; | ||
if (response.statusMessage) { | ||
message = response.statusMessage; | ||
} | ||
this.logger.debug("Could not upload profile: " + message + "."); | ||
return [2 /*return*/]; | ||
} | ||
this.logger.debug("Successfully uploaded profile " + prof.profileType + "."); | ||
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*/]; | ||
profileAndUpload(prof) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
prof = yield this.profile(prof); | ||
this.logger.debug(`Successfully collected profile ${prof.profileType}.`); | ||
prof.labels = this.profileLabels; | ||
} | ||
catch (err) { | ||
this.logger.debug(`Failed to collect profile: ${err}`); | ||
return; | ||
} | ||
const options = { | ||
method: 'PATCH', | ||
uri: this.config.baseApiUrl + '/' + prof.name, | ||
body: prof, | ||
json: true, | ||
}; | ||
try { | ||
const res = yield this.request(options); | ||
if (isErrorResponseStatusCode(res.statusCode)) { | ||
let message = res.statusCode; | ||
if (res.statusMessage) { | ||
message = res.statusMessage; | ||
} | ||
this.logger.debug(`Could not upload profile: ${message}.`); | ||
return; | ||
} | ||
}); | ||
this.logger.debug(`Successfully uploaded profile ${prof.profileType}.`); | ||
} | ||
catch (err) { | ||
this.logger.debug(`Failed to upload profile: ${err}`); | ||
} | ||
}); | ||
}; | ||
} | ||
/** | ||
@@ -468,22 +393,14 @@ * Collects a profile of the type specified by profileType field of prof. | ||
*/ | ||
Profiler.prototype.profile = function (prof) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var _a; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
_a = prof.profileType; | ||
switch (_a) { | ||
case ProfileTypes.Wall: return [3 /*break*/, 1]; | ||
case ProfileTypes.Heap: return [3 /*break*/, 3]; | ||
} | ||
return [3 /*break*/, 4]; | ||
case 1: return [4 /*yield*/, this.writeTimeProfile(prof)]; | ||
case 2: return [2 /*return*/, _b.sent()]; | ||
case 3: return [2 /*return*/, this.writeHeapProfile(prof)]; | ||
case 4: throw new Error("Unexpected profile type " + prof.profileType + "."); | ||
} | ||
}); | ||
profile(prof) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
switch (prof.profileType) { | ||
case ProfileTypes.Wall: | ||
return yield this.writeTimeProfile(prof); | ||
case ProfileTypes.Heap: | ||
return this.writeHeapProfile(prof); | ||
default: | ||
throw new Error(`Unexpected profile type ${prof.profileType}.`); | ||
} | ||
}); | ||
}; | ||
} | ||
/** | ||
@@ -495,31 +412,20 @@ * Collects a time profile, converts profile to compressed, base64 encoded | ||
*/ | ||
Profiler.prototype.writeTimeProfile = function (prof) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var durationMillis, p, _a; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
if (!this.timeProfiler) { | ||
throw Error('Cannot collect time profile, time profiler not enabled.'); | ||
} | ||
if (prof.duration === undefined) { | ||
throw Error('Cannot collect time profile, duration is undefined.'); | ||
} | ||
durationMillis = parseDuration(prof.duration); | ||
if (!durationMillis) { | ||
throw Error("Cannot collect time profile, duration \"" + prof.duration + "\" cannot" + | ||
" be parsed."); | ||
} | ||
return [4 /*yield*/, this.timeProfiler.profile(durationMillis)]; | ||
case 1: | ||
p = _b.sent(); | ||
_a = prof; | ||
return [4 /*yield*/, profileBytes(p)]; | ||
case 2: | ||
_a.profileBytes = _b.sent(); | ||
return [2 /*return*/, prof]; | ||
} | ||
}); | ||
writeTimeProfile(prof) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!this.timeProfiler) { | ||
throw Error('Cannot collect time profile, time profiler not enabled.'); | ||
} | ||
if (prof.duration === undefined) { | ||
throw Error('Cannot collect time profile, duration is undefined.'); | ||
} | ||
const durationMillis = parseDuration(prof.duration); | ||
if (!durationMillis) { | ||
throw Error(`Cannot collect time profile, duration "${prof.duration}" cannot` + | ||
` be parsed.`); | ||
} | ||
const p = yield this.timeProfiler.profile(durationMillis); | ||
prof.profileBytes = yield profileBytes(p); | ||
return prof; | ||
}); | ||
}; | ||
} | ||
/** | ||
@@ -531,24 +437,14 @@ * Collects a heap profile, converts profile to compressed, base64 encoded | ||
*/ | ||
Profiler.prototype.writeHeapProfile = function (prof) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var p, _a; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
if (!this.heapProfiler) { | ||
throw Error('Cannot collect heap profile, heap profiler not enabled.'); | ||
} | ||
p = this.heapProfiler.profile(); | ||
_a = prof; | ||
return [4 /*yield*/, profileBytes(p)]; | ||
case 1: | ||
_a.profileBytes = _b.sent(); | ||
return [2 /*return*/, prof]; | ||
} | ||
}); | ||
writeHeapProfile(prof) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (this.config.disableHeap) { | ||
throw Error('Cannot collect heap profile, heap profiler not enabled.'); | ||
} | ||
const p = heapProfiler.profile(); | ||
prof.profileBytes = yield profileBytes(p); | ||
return prof; | ||
}); | ||
}; | ||
return Profiler; | ||
}(exports.common.ServiceObject)); | ||
} | ||
} | ||
exports.Profiler = Profiler; | ||
//# sourceMappingURL=profiler.js.map |
@@ -17,18 +17,12 @@ /** | ||
import { perftools } from '../../../proto/profile'; | ||
export declare class HeapProfiler { | ||
private intervalBytes; | ||
private stackDepth; | ||
private enabled; | ||
/** | ||
* @param intervalBytes - average bytes between samples. | ||
* @param stackDepth - upper limit on number of frames in stack for sample. | ||
*/ | ||
constructor(intervalBytes: number, stackDepth: number); | ||
/** | ||
* Collects a heap profile when heapProfiler is enabled. Otherwise throws | ||
* an error. | ||
*/ | ||
profile(): perftools.profiles.IProfile; | ||
enable(): void; | ||
disable(): void; | ||
} | ||
export declare function profile(): perftools.profiles.IProfile; | ||
/** | ||
* Starts heap profiling. If heap profiling has already been started with | ||
* the same parameters, this is a noop. If heap profiler has already been | ||
* started with different parameters, this throws an error. | ||
* | ||
* @param intervalBytes - average number of bytes between samples. | ||
* @param stackDepth - maximum stack depth for samples collected. | ||
*/ | ||
export declare function start(intervalBytes: number, stackDepth: number): void; | ||
export declare function stop(): void; |
@@ -18,38 +18,58 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var profile_serializer_1 = require("./profile-serializer"); | ||
var profiler = require('bindings')('sampling_heap_profiler'); | ||
var HeapProfiler = /** @class */ (function () { | ||
/** | ||
* @param intervalBytes - average bytes between samples. | ||
* @param stackDepth - upper limit on number of frames in stack for sample. | ||
*/ | ||
function HeapProfiler(intervalBytes, stackDepth) { | ||
this.intervalBytes = intervalBytes; | ||
this.stackDepth = stackDepth; | ||
this.enabled = false; | ||
this.enable(); | ||
const profile_serializer_1 = require("./profile-serializer"); | ||
const profiler = require('bindings')('sampling_heap_profiler'); | ||
let enabled = false; | ||
let heapIntervalBytes = 0; | ||
let heapStackDepth = 0; | ||
/* | ||
* Collects a heap profile when heapProfiler is enabled. Otherwise throws | ||
* an error. | ||
*/ | ||
function profile() { | ||
if (!enabled) { | ||
throw new Error('Heap profiler is not enabled.'); | ||
} | ||
/** | ||
* Collects a heap profile when heapProfiler is enabled. Otherwise throws | ||
* an error. | ||
*/ | ||
HeapProfiler.prototype.profile = function () { | ||
if (!this.enabled) { | ||
throw new Error('Heap profiler is not enabled.'); | ||
} | ||
var result = profiler.getAllocationProfile(); | ||
var startTimeNanos = Date.now() * 1000 * 1000; | ||
return profile_serializer_1.serializeHeapProfile(result, startTimeNanos, this.intervalBytes); | ||
const startTimeNanos = Date.now() * 1000 * 1000; | ||
const result = profiler.getAllocationProfile(); | ||
// Add node for external memory usage. | ||
// Current type definitions do not have external. | ||
// TODO: remove any once type definition is updated to include external. | ||
// tslint:disable-next-line: no-any | ||
const { external } = process.memoryUsage(); | ||
const externalNode = { | ||
name: '(external)', | ||
scriptName: '', | ||
children: [], | ||
allocations: [{ sizeBytes: external, count: 1 }], | ||
}; | ||
HeapProfiler.prototype.enable = function () { | ||
profiler.startSamplingHeapProfiler(this.intervalBytes, this.stackDepth); | ||
this.enabled = true; | ||
}; | ||
HeapProfiler.prototype.disable = function () { | ||
this.enabled = false; | ||
result.children.push(externalNode); | ||
return profile_serializer_1.serializeHeapProfile(result, startTimeNanos, heapIntervalBytes); | ||
} | ||
exports.profile = profile; | ||
/** | ||
* Starts heap profiling. If heap profiling has already been started with | ||
* the same parameters, this is a noop. If heap profiler has already been | ||
* started with different parameters, this throws an error. | ||
* | ||
* @param intervalBytes - average number of bytes between samples. | ||
* @param stackDepth - maximum stack depth for samples collected. | ||
*/ | ||
function start(intervalBytes, stackDepth) { | ||
if (enabled) { | ||
throw new Error(`Heap profiler is already started with intervalBytes ${heapIntervalBytes} and stackDepth ${stackDepth}`); | ||
} | ||
heapIntervalBytes = intervalBytes; | ||
heapStackDepth = stackDepth; | ||
profiler.startSamplingHeapProfiler(heapIntervalBytes, heapStackDepth); | ||
enabled = true; | ||
} | ||
exports.start = start; | ||
// Stops heap profiling. If heap profiling has not been started, does nothing. | ||
function stop() { | ||
if (enabled) { | ||
enabled = false; | ||
profiler.stopSamplingHeapProfiler(); | ||
}; | ||
return HeapProfiler; | ||
}()); | ||
exports.HeapProfiler = HeapProfiler; | ||
} | ||
} | ||
exports.stop = stop; | ||
//# sourceMappingURL=heap-profiler.js.map |
@@ -18,3 +18,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var profile_1 = require("../../../proto/profile"); | ||
const profile_1 = require("../../../proto/profile"); | ||
/** | ||
@@ -24,4 +24,4 @@ * Used to build string table and access strings and their ids within the table | ||
*/ | ||
var StringTable = /** @class */ (function () { | ||
function StringTable() { | ||
class StringTable { | ||
constructor() { | ||
this.strings = []; | ||
@@ -35,4 +35,4 @@ this.stringsMap = new Map(); | ||
*/ | ||
StringTable.prototype.getIndexOrAdd = function (str) { | ||
var idx = this.stringsMap.get(str); | ||
getIndexOrAdd(str) { | ||
let idx = this.stringsMap.get(str); | ||
if (idx !== undefined) { | ||
@@ -44,5 +44,4 @@ return idx; | ||
return idx; | ||
}; | ||
return StringTable; | ||
}()); | ||
} | ||
} | ||
/** | ||
@@ -61,19 +60,18 @@ * Takes v8 profile and populates sample, location, and function fields of | ||
function serialize(profile, root, appendToSamples, stringTable) { | ||
var samples = []; | ||
var locations = []; | ||
var functions = []; | ||
var locationMap = new Map(); | ||
var functionMap = new Map(); | ||
var functionIdMap = new Map(); | ||
var locationIdMap = new Map(); | ||
var entries = root.children.map(function (n) { return ({ node: n, stack: [] }); }); | ||
const samples = []; | ||
const locations = []; | ||
const functions = []; | ||
const locationMap = new Map(); | ||
const functionMap = new Map(); | ||
const functionIdMap = new Map(); | ||
const locationIdMap = new Map(); | ||
const entries = root.children.map((n) => ({ node: n, stack: [] })); | ||
while (entries.length > 0) { | ||
var entry = entries.pop(); | ||
var node = entry.node; | ||
var stack = entry.stack; | ||
var location = getLocation(node); | ||
const entry = entries.pop(); | ||
const node = entry.node; | ||
const stack = entry.stack; | ||
const location = getLocation(node); | ||
stack.unshift(location.id); | ||
appendToSamples(entry, samples); | ||
for (var _i = 0, _a = node.children; _i < _a.length; _i++) { | ||
var child = _a[_i]; | ||
for (const child of node.children) { | ||
entries.push({ node: child, stack: stack.slice() }); | ||
@@ -87,4 +85,4 @@ } | ||
function getLocation(node) { | ||
var keyStr = node.scriptId + ":" + node.lineNumber + ":" + node.columnNumber + ":" + node.name; | ||
var id = locationIdMap.get(keyStr); | ||
const keyStr = `${node.scriptId}:${node.lineNumber}:${node.columnNumber}:${node.name}`; | ||
let id = locationIdMap.get(keyStr); | ||
if (id !== undefined) { | ||
@@ -96,3 +94,3 @@ // id is index+1, since 0 is not valid id. | ||
locationIdMap.set(keyStr, id); | ||
var location = new profile_1.perftools.profiles.Location({ id: id, line: [getLine(node)] }); | ||
const location = new profile_1.perftools.profiles.Location({ id, line: [getLine(node)] }); | ||
locations.push(location); | ||
@@ -108,4 +106,4 @@ return location; | ||
function getFunction(node) { | ||
var keyStr = node.scriptId + ":" + node.name; | ||
var id = functionIdMap.get(keyStr); | ||
const keyStr = `${node.scriptId}:${node.name}`; | ||
let id = functionIdMap.get(keyStr); | ||
if (id !== undefined) { | ||
@@ -117,5 +115,5 @@ // id is index+1, since 0 is not valid id. | ||
functionIdMap.set(keyStr, id); | ||
var nameId = stringTable.getIndexOrAdd(node.name || '(anonymous)'); | ||
var f = new profile_1.perftools.profiles.Function({ | ||
id: id, | ||
const nameId = stringTable.getIndexOrAdd(node.name || '(anonymous)'); | ||
const f = new profile_1.perftools.profiles.Function({ | ||
id, | ||
name: nameId, | ||
@@ -174,5 +172,5 @@ systemName: nameId, | ||
function serializeTimeProfile(prof, intervalMicros) { | ||
var appendTimeEntryToSamples = function (entry, samples) { | ||
const appendTimeEntryToSamples = (entry, samples) => { | ||
if (entry.node.hitCount > 0) { | ||
var sample = new profile_1.perftools.profiles.Sample({ | ||
const sample = new profile_1.perftools.profiles.Sample({ | ||
locationId: entry.stack, | ||
@@ -184,6 +182,6 @@ value: [entry.node.hitCount, entry.node.hitCount * intervalMicros] | ||
}; | ||
var stringTable = new StringTable(); | ||
var sampleValueType = createSampleCountValueType(stringTable); | ||
var timeValueType = createTimeValueType(stringTable); | ||
var profile = { | ||
const stringTable = new StringTable(); | ||
const sampleValueType = createSampleCountValueType(stringTable); | ||
const timeValueType = createTimeValueType(stringTable); | ||
const profile = { | ||
sampleType: [sampleValueType, timeValueType], | ||
@@ -210,7 +208,6 @@ timeNanos: Date.now() * 1000 * 1000, | ||
function serializeHeapProfile(prof, startTimeNanos, intervalBytes) { | ||
var appendHeapEntryToSamples = function (entry, samples) { | ||
const appendHeapEntryToSamples = (entry, samples) => { | ||
if (entry.node.allocations.length > 0) { | ||
for (var _i = 0, _a = entry.node.allocations; _i < _a.length; _i++) { | ||
var alloc = _a[_i]; | ||
var sample = new profile_1.perftools.profiles.Sample({ | ||
for (const alloc of entry.node.allocations) { | ||
const sample = new profile_1.perftools.profiles.Sample({ | ||
locationId: entry.stack, | ||
@@ -224,6 +221,6 @@ value: [alloc.count, alloc.sizeBytes * alloc.count] | ||
}; | ||
var stringTable = new StringTable(); | ||
var sampleValueType = createObjectCountValueType(stringTable); | ||
var allocationValueType = createAllocationValueType(stringTable); | ||
var profile = { | ||
const stringTable = new StringTable(); | ||
const sampleValueType = createObjectCountValueType(stringTable); | ||
const allocationValueType = createAllocationValueType(stringTable); | ||
const profile = { | ||
sampleType: [sampleValueType, allocationValueType], | ||
@@ -230,0 +227,0 @@ timeNanos: startTimeNanos, |
@@ -0,1 +1,16 @@ | ||
/** | ||
* Copyright 2017 Google Inc. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
import { perftools } from '../../../proto/profile'; | ||
@@ -2,0 +17,0 @@ export declare class TimeProfiler { |
@@ -25,38 +25,11 @@ "use strict"; | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [0, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var delay = require("delay"); | ||
var profile_serializer_1 = require("./profile-serializer"); | ||
var profiler = require('bindings')('time_profiler'); | ||
var TimeProfiler = /** @class */ (function () { | ||
const delay = require("delay"); | ||
const profile_serializer_1 = require("./profile-serializer"); | ||
const profiler = require('bindings')('time_profiler'); | ||
class TimeProfiler { | ||
/** | ||
* @param intervalMicros - average time in microseconds between samples | ||
*/ | ||
function TimeProfiler(intervalMicros) { | ||
constructor(intervalMicros) { | ||
this.intervalMicros = intervalMicros; | ||
@@ -70,22 +43,23 @@ profiler.setSamplingInterval(this.intervalMicros); | ||
*/ | ||
TimeProfiler.prototype.profile = function (durationMillis) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var runName, result; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
runName = 'stackdriver-profiler-' + Date.now() + '-' + Math.random(); | ||
profiler.startProfiling(runName); | ||
return [4 /*yield*/, delay(durationMillis)]; | ||
case 1: | ||
_a.sent(); | ||
result = profiler.stopProfiling(runName); | ||
return [2 /*return*/, profile_serializer_1.serializeTimeProfile(result, this.intervalMicros)]; | ||
} | ||
}); | ||
profile(durationMillis) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Node.js contains an undocumented API for reporting idle status to V8. | ||
// This lets the profiler distinguish idle time from time spent in native | ||
// code. Ideally this should be default behavior. Until then, use the | ||
// undocumented API. | ||
// See https://github.com/nodejs/node/issues/19009#issuecomment-403161559. | ||
// tslint:disable-next-line no-any | ||
process._startProfilerIdleNotifier(); | ||
const runName = 'stackdriver-profiler-' + Date.now() + '-' + Math.random(); | ||
profiler.startProfiling(runName); | ||
yield delay(durationMillis); | ||
const result = profiler.stopProfiling(runName); | ||
// tslint:disable-next-line no-any | ||
process._stopProfilerIdleNotifier(); | ||
const profile = profile_serializer_1.serializeTimeProfile(result, this.intervalMicros); | ||
return profile; | ||
}); | ||
}; | ||
return TimeProfiler; | ||
}()); | ||
} | ||
} | ||
exports.TimeProfiler = TimeProfiler; | ||
//# sourceMappingURL=time-profiler.js.map |
@@ -26,5 +26,5 @@ /** | ||
scriptName: string; | ||
scriptId: number; | ||
lineNumber: number; | ||
columnNumber: number; | ||
scriptId?: number; | ||
lineNumber?: number; | ||
columnNumber?: number; | ||
children: ProfileNode[]; | ||
@@ -31,0 +31,0 @@ } |
{ | ||
"name": "@google-cloud/profiler", | ||
"version": "0.1.14", | ||
"version": "0.2.0", | ||
"description": "Adds support for Google Cloud Profiler to node.js applications", | ||
@@ -29,7 +29,7 @@ "repository": "GoogleCloudPlatform/cloud-profiler-nodejs", | ||
"dependencies": { | ||
"@google-cloud/common": "^0.17.0", | ||
"@google-cloud/common": "^0.20.3", | ||
"bindings": "^1.2.1", | ||
"delay": "^2.0.0", | ||
"delay": "^3.0.0", | ||
"extend": "^3.0.1", | ||
"gcp-metadata": "^0.6.1", | ||
"gcp-metadata": "^0.7.0", | ||
"nan": "^2.8.0", | ||
@@ -39,5 +39,5 @@ "parse-duration": "^0.1.1", | ||
"pretty-ms": "^3.1.0", | ||
"protobufjs": "~6.8.0", | ||
"protobufjs": "~6.8.6", | ||
"request": "^2.83.0", | ||
"retry-request": "^3.0.1", | ||
"retry-request": "^4.0.0", | ||
"semver": "^5.5.0" | ||
@@ -48,6 +48,6 @@ }, | ||
"@types/extend": "^3.0.0", | ||
"@types/long": "^3.0.32", | ||
"@types/long": "^4.0.0", | ||
"@types/mocha": "^5.0.0", | ||
"@types/nock": "^9.1.0", | ||
"@types/node": "^9.3.0", | ||
"@types/node": "^10.0.3", | ||
"@types/pify": "^3.0.0", | ||
@@ -57,13 +57,14 @@ "@types/pretty-ms": "^3.0.0", | ||
"@types/semver": "^5.5.0", | ||
"@types/sinon": "^4.0.0", | ||
"@types/sinon": "^5.0.1", | ||
"codecov": "^3.0.0", | ||
"gts": "^0.5.3", | ||
"deep-copy": "^1.4.2", | ||
"gts": "^0.8.0", | ||
"js-green-licenses": "^0.5.0", | ||
"mocha": "^5.0.0", | ||
"nock": "^9.0.22", | ||
"nyc": "^11.2.1", | ||
"sinon": "^5.0.0", | ||
"nyc": "^12.0.2", | ||
"sinon": "^6.0.0", | ||
"source-map-support": "^0.5.0", | ||
"ts-mockito": "^2.2.5", | ||
"typescript": "~2.7.x" | ||
"typescript": "~2.9.0" | ||
}, | ||
@@ -88,4 +89,4 @@ "files": [ | ||
"engines": { | ||
"node": "6.12.3 - 7.0.0 || >=8.9.4" | ||
"node": ">=6.12.3 <8.0.0 || >=8.9.4 <10.0.0 || >=10.4.1" | ||
} | ||
} |
@@ -17,4 +17,17 @@ # Google Cloud Profiler | ||
1. Your application will need to be using Node.js version 6.12.3 or greater, | ||
or Node.js 8.9.4 or greater. | ||
1. Your application will need to be using Node.js version 6.12.3 or greater, | ||
Node.js 8.9.4 or greater, or Node.js 10.4.1 or greater. The profiler will not | ||
be enabled when using earlier versions of Node 6, 8, and 10 because the | ||
profiler is not stable with those versions of Node.js. | ||
* Versions of Node.js 6 prior to 6.12.3 are impacted by | ||
[this](https://bugs.chromium.org/p/v8/issues/detail?id=4959) issue, which can | ||
cause segmentation faults when heap profiling is enabled. | ||
* Versions of Node.js before Node.js 8 prior to 8.9.4 are impacted by | ||
[this](https://bugs.chromium.org/p/v8/issues/detail?id=6623) issue, which | ||
causes a memory leak when time profiling is enabled. | ||
* Versions of Node.js 10 prior to 10.4.1 are impacted by | ||
[this](https://bugs.chromium.org/p/chromium/issues/detail?id=847863) issue, | ||
which can cause garbage collection to take several minutes when heap | ||
profiling is enabled. | ||
1. You will need a project in the [Google Developers Console][cloud-console]. | ||
@@ -21,0 +34,0 @@ Your application can run anywhere, but the profiler data is associated with a |
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 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 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
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
275
1167511
22
33
22740
+ Added@google-cloud/common@0.20.3(transitive)
+ Added@types/caseless@0.12.5(transitive)
+ Added@types/duplexify@3.6.4(transitive)
+ Added@types/node@22.9.0(transitive)
+ Added@types/request@2.48.12(transitive)
+ Added@types/tough-cookie@4.0.5(transitive)
+ Addeddebug@4.3.7(transitive)
+ Addeddelay@3.1.0(transitive)
+ Addedform-data@2.5.2(transitive)
+ Addedgcp-metadata@0.7.0(transitive)
+ Addedms@2.1.3(transitive)
+ Addedretry-request@4.2.2(transitive)
+ Addedsplit-array-stream@2.0.0(transitive)
+ Addedundici-types@6.19.8(transitive)
- Removed@google-cloud/common@0.17.0(transitive)
- Removedarray-uniq@1.0.3(transitive)
- Removedasync@2.6.4(transitive)
- Removedbuffer-from@1.1.2(transitive)
- Removedcapture-stack-trace@1.0.2(transitive)
- Removedconcat-stream@1.6.2(transitive)
- Removedcreate-error-class@3.0.2(transitive)
- Removeddelay@2.0.0(transitive)
- Removedgoogle-auto-auth@0.10.1(transitive)
- Removedlodash@4.17.21(transitive)
- Removedlog-driver@1.2.7(transitive)
- Removedmethmeth@1.1.0(transitive)
- Removedmodelo@4.2.3(transitive)
- Removedp-defer@1.0.0(transitive)
- Removedretry-request@3.3.2(transitive)
- Removedsplit-array-stream@1.0.3(transitive)
- Removedstring-format-obj@1.1.1(transitive)
- Removedtypedarray@0.0.6(transitive)
Updated@google-cloud/common@^0.20.3
Updateddelay@^3.0.0
Updatedgcp-metadata@^0.7.0
Updatedprotobufjs@~6.8.6
Updatedretry-request@^4.0.0