Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚡ Tweaks to diagnostic events #2544

Merged
merged 2 commits into from
Dec 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/cli/commands/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
CredentialTypes,
Db,
ExternalHooks,
GenericHelpers,
InternalHooksManager,
IWorkflowBase,
IWorkflowExecutionDataProcess,
Expand Down Expand Up @@ -125,7 +126,8 @@ export class Execute extends Command {
await externalHooks.init();

const instanceId = await UserSettings.getInstanceId();
InternalHooksManager.init(instanceId);
const { cli } = await GenericHelpers.getVersions();
InternalHooksManager.init(instanceId, cli);

// Add the found types to an instance other parts of the application can use
const nodeTypes = NodeTypes();
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/commands/executeBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
CredentialTypes,
Db,
ExternalHooks,
GenericHelpers,
InternalHooksManager,
IWorkflowDb,
IWorkflowExecutionDataProcess,
Expand Down Expand Up @@ -305,7 +306,8 @@ export class ExecuteBatch extends Command {
await externalHooks.init();

const instanceId = await UserSettings.getInstanceId();
InternalHooksManager.init(instanceId);
const { cli } = await GenericHelpers.getVersions();
InternalHooksManager.init(instanceId, cli);

// Add the found types to an instance other parts of the application can use
const nodeTypes = NodeTypes();
Expand Down
14 changes: 2 additions & 12 deletions packages/cli/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,6 @@ export class Start extends Command {
LoggerProxy.init(logger);
logger.info('Initializing n8n process');

logger.info(
'\n' +
'****************************************************\n' +
'* *\n' +
'* n8n now sends selected, anonymous telemetry. *\n' +
'* For more details (and how to opt out): *\n' +
'* https://docs.n8n.io/reference/telemetry.html *\n' +
'* *\n' +
'****************************************************\n',
);

// Start directly with the init of the database to improve startup time
const startDbInitPromise = Db.init().catch((error: Error) => {
logger.error(`There was an error initializing DB: "${error.message}"`);
Expand Down Expand Up @@ -313,7 +302,8 @@ export class Start extends Command {
}

const instanceId = await UserSettings.getInstanceId();
InternalHooksManager.init(instanceId);
const { cli } = await GenericHelpers.getVersions();
InternalHooksManager.init(instanceId, cli);

await Server.start();

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/commands/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ export class Webhook extends Command {
await startDbInitPromise;

const instanceId = await UserSettings.getInstanceId();
InternalHooksManager.init(instanceId);
const { cli } = await GenericHelpers.getVersions();
InternalHooksManager.init(instanceId, cli);

if (config.get('executions.mode') === 'queue') {
const redisHost = config.get('queue.bull.redis.host');
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/commands/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,10 @@ export class Worker extends Command {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Worker.jobQueue.process(flags.concurrency, async (job) => this.runJob(job, nodeTypes));

const versions = await GenericHelpers.getVersions();
const instanceId = await UserSettings.getInstanceId();
InternalHooksManager.init(instanceId);

const versions = await GenericHelpers.getVersions();
InternalHooksManager.init(instanceId, versions.cli);

console.info('\nn8n worker is now ready');
console.info(` * Version: ${versions.cli}`);
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,10 @@ export interface IDiagnosticInfo {

export interface IInternalHooksClass {
onN8nStop(): Promise<void>;
onServerStarted(diagnosticInfo: IDiagnosticInfo): Promise<unknown[]>;
onServerStarted(
diagnosticInfo: IDiagnosticInfo,
firstWorkflowCreatedAt?: Date,
): Promise<unknown[]>;
onPersonalizationSurveySubmitted(answers: IPersonalizationSurveyAnswers): Promise<void>;
onWorkflowCreated(workflow: IWorkflowBase): Promise<void>;
onWorkflowDeleted(workflowId: string): Promise<void>;
Expand Down
29 changes: 24 additions & 5 deletions packages/cli/src/InternalHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ import {
import { Telemetry } from './telemetry';

export class InternalHooksClass implements IInternalHooksClass {
constructor(private telemetry: Telemetry) {}
private versionCli: string;

async onServerStarted(diagnosticInfo: IDiagnosticInfo): Promise<unknown[]> {
constructor(private telemetry: Telemetry, versionCli: string) {
this.versionCli = versionCli;
}

async onServerStarted(
diagnosticInfo: IDiagnosticInfo,
earliestWorkflowCreatedAt?: Date,
): Promise<unknown[]> {
const info = {
version_cli: diagnosticInfo.versionCli,
db_type: diagnosticInfo.databaseType,
Expand All @@ -25,7 +32,10 @@ export class InternalHooksClass implements IInternalHooksClass {

return Promise.all([
this.telemetry.identify(info),
this.telemetry.track('Instance started', info),
this.telemetry.track('Instance started', {
...info,
earliest_workflow_created: earliestWorkflowCreatedAt,
}),
]);
}

Expand All @@ -39,9 +49,11 @@ export class InternalHooksClass implements IInternalHooksClass {
}

async onWorkflowCreated(workflow: IWorkflowBase): Promise<void> {
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow);
return this.telemetry.track('User created workflow', {
workflow_id: workflow.id,
node_graph: TelemetryHelpers.generateNodesGraph(workflow).nodeGraph,
node_graph: nodeGraph,
node_graph_string: JSON.stringify(nodeGraph),
});
}

Expand All @@ -52,16 +64,21 @@ export class InternalHooksClass implements IInternalHooksClass {
}

async onWorkflowSaved(workflow: IWorkflowBase): Promise<void> {
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow);

return this.telemetry.track('User saved workflow', {
workflow_id: workflow.id,
node_graph: TelemetryHelpers.generateNodesGraph(workflow).nodeGraph,
node_graph: nodeGraph,
node_graph_string: JSON.stringify(nodeGraph),
version_cli: this.versionCli,
});
}

async onWorkflowPostExecute(workflow: IWorkflowBase, runData?: IRun): Promise<void> {
const properties: IDataObject = {
workflow_id: workflow.id,
is_manual: false,
version_cli: this.versionCli,
};

if (runData !== undefined) {
Expand Down Expand Up @@ -92,6 +109,8 @@ export class InternalHooksClass implements IInternalHooksClass {
if (properties.is_manual) {
const nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow);
properties.node_graph = nodeGraphResult.nodeGraph;
properties.node_graph_string = JSON.stringify(nodeGraphResult.nodeGraph);

if (errorNodeName) {
properties.error_node_id = nodeGraphResult.nameIndices[errorNodeName];
}
Expand Down
7 changes: 5 additions & 2 deletions packages/cli/src/InternalHooksManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ export class InternalHooksManager {
throw new Error('InternalHooks not initialized');
}

static init(instanceId: string): InternalHooksClass {
static init(instanceId: string, versionCli: string): InternalHooksClass {
if (!this.internalHooksInstance) {
this.internalHooksInstance = new InternalHooksClass(new Telemetry(instanceId));
this.internalHooksInstance = new InternalHooksClass(
new Telemetry(instanceId, versionCli),
versionCli,
);
}

return this.internalHooksInstance;
Expand Down
9 changes: 8 additions & 1 deletion packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2896,7 +2896,14 @@ export async function start(): Promise<void> {
deploymentType: config.get('deployment.type'),
};

void InternalHooksManager.getInstance().onServerStarted(diagnosticInfo);
void Db.collections
.Workflow!.findOne({
select: ['createdAt'],
order: { createdAt: 'ASC' },
})
.then(async (workflow) =>
InternalHooksManager.getInstance().onServerStarted(diagnosticInfo, workflow?.createdAt),
mutdmour marked this conversation as resolved.
Show resolved Hide resolved
);
});
}

Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/WorkflowRunnerProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
CredentialTypes,
Db,
ExternalHooks,
GenericHelpers,
IWorkflowExecuteProcess,
IWorkflowExecutionDataProcessWithExecution,
NodeTypes,
Expand Down Expand Up @@ -137,7 +138,8 @@ export class WorkflowRunnerProcess {
await externalHooks.init();

const instanceId = (await UserSettings.prepareUserSettings()).instanceId ?? '';
InternalHooksManager.init(instanceId);
const { cli } = await GenericHelpers.getVersions();
InternalHooksManager.init(instanceId, cli);

// Credentials should now be loaded from database.
// We check if any node uses credentials. If it does, then
Expand Down
92 changes: 71 additions & 21 deletions packages/cli/src/telemetry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,57 @@ import { IDataObject, LoggerProxy } from 'n8n-workflow';
import config = require('../../config');
import { getLogger } from '../Logger';

interface IExecutionCountsBufferItem {
manual_success_count: number;
manual_error_count: number;
prod_success_count: number;
prod_error_count: number;
}
type CountBufferItemKey =
| 'manual_success_count'
| 'manual_error_count'
| 'prod_success_count'
| 'prod_error_count';

type FirstExecutionItemKey =
| 'first_manual_success'
| 'first_manual_error'
| 'first_prod_success'
| 'first_prod_error';

type IExecutionCountsBufferItem = {
[key in CountBufferItemKey]: number;
};

interface IExecutionCountsBuffer {
[workflowId: string]: IExecutionCountsBufferItem;
}

type IFirstExecutions = {
[key in FirstExecutionItemKey]: Date | undefined;
};

interface IExecutionsBuffer {
counts: IExecutionCountsBuffer;
firstExecutions: IFirstExecutions;
}

export class Telemetry {
private client?: TelemetryClient;

private instanceId: string;

private pulseIntervalReference: NodeJS.Timeout;
private versionCli: string;

private executionCountsBuffer: IExecutionCountsBuffer = {};
private pulseIntervalReference: NodeJS.Timeout;

constructor(instanceId: string) {
private executionCountsBuffer: IExecutionsBuffer = {
counts: {},
firstExecutions: {
first_manual_error: undefined,
first_manual_success: undefined,
first_prod_error: undefined,
first_prod_success: undefined,
},
};

constructor(instanceId: string, versionCli: string) {
this.instanceId = instanceId;
this.versionCli = versionCli;

const enabled = config.get('diagnostics.enabled') as boolean;
if (enabled) {
Expand All @@ -53,33 +82,41 @@ export class Telemetry {
return Promise.resolve();
}

const allPromises = Object.keys(this.executionCountsBuffer).map(async (workflowId) => {
const allPromises = Object.keys(this.executionCountsBuffer.counts).map(async (workflowId) => {
const promise = this.track('Workflow execution count', {
version_cli: this.versionCli,
workflow_id: workflowId,
...this.executionCountsBuffer[workflowId],
...this.executionCountsBuffer.counts[workflowId],
...this.executionCountsBuffer.firstExecutions,
});
this.executionCountsBuffer[workflowId].manual_error_count = 0;
this.executionCountsBuffer[workflowId].manual_success_count = 0;
this.executionCountsBuffer[workflowId].prod_error_count = 0;
this.executionCountsBuffer[workflowId].prod_success_count = 0;

this.executionCountsBuffer.counts[workflowId].manual_error_count = 0;
this.executionCountsBuffer.counts[workflowId].manual_success_count = 0;
this.executionCountsBuffer.counts[workflowId].prod_error_count = 0;
this.executionCountsBuffer.counts[workflowId].prod_success_count = 0;

return promise;
});

allPromises.push(this.track('pulse'));
allPromises.push(this.track('pulse', { version_cli: this.versionCli }));
return Promise.all(allPromises);
}

async trackWorkflowExecution(properties: IDataObject): Promise<void> {
if (this.client) {
const workflowId = properties.workflow_id as string;
this.executionCountsBuffer[workflowId] = this.executionCountsBuffer[workflowId] ?? {
this.executionCountsBuffer.counts[workflowId] = this.executionCountsBuffer.counts[
mutdmour marked this conversation as resolved.
Show resolved Hide resolved
workflowId
] ?? {
manual_error_count: 0,
manual_success_count: 0,
prod_error_count: 0,
prod_success_count: 0,
};

let countKey: CountBufferItemKey;
let firstExecKey: FirstExecutionItemKey;

if (
properties.success === false &&
properties.error_node_type &&
Expand All @@ -89,15 +126,28 @@ export class Telemetry {
void this.track('Workflow execution errored', properties);

if (properties.is_manual) {
this.executionCountsBuffer[workflowId].manual_error_count++;
firstExecKey = 'first_manual_error';
countKey = 'manual_error_count';
} else {
this.executionCountsBuffer[workflowId].prod_error_count++;
firstExecKey = 'first_prod_error';
countKey = 'prod_error_count';
}
} else if (properties.is_manual) {
this.executionCountsBuffer[workflowId].manual_success_count++;
countKey = 'manual_success_count';
firstExecKey = 'first_manual_success';
} else {
this.executionCountsBuffer[workflowId].prod_success_count++;
countKey = 'prod_success_count';
firstExecKey = 'first_prod_success';
}

if (
!this.executionCountsBuffer.firstExecutions[firstExecKey] &&
this.executionCountsBuffer.counts[workflowId][countKey] === 0
) {
this.executionCountsBuffer.firstExecutions[firstExecKey] = new Date();
}

this.executionCountsBuffer.counts[workflowId][countKey]++;
}
}

Expand Down
10 changes: 8 additions & 2 deletions packages/editor-ui/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@
</template>

<script lang="ts">
import Vue from 'vue';
import Telemetry from './components/Telemetry.vue';

export default {
export default Vue.extend({
name: 'App',
components: {
Telemetry,
},
};
watch: {
'$route'(route) {
this.$telemetry.page('Editor', route.name);
},
},
});
</script>

<style lang="scss">
Expand Down
Loading