Skip to content
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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,7 @@ module.exports = {
files: [
'x-pack/plugins/observability_solution/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)',
'src/plugins/ai_assistant_management/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)',
'x-pack/packages/observability/logs_overview/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)',
],
rules: {
'@kbn/i18n/strings_should_be_translated_with_i18n': 'warn',
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0",
"@types/react": "~18.2.0",
"@types/react-dom": "~18.2.0",
"@xstate5/react/**/xstate": "^5.18.1",
"globby/fast-glob": "^3.2.11"
},
"dependencies": {
Expand Down Expand Up @@ -687,6 +688,7 @@
"@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability",
"@kbn/observability-get-padded-alert-time-range-util": "link:x-pack/packages/observability/get_padded_alert_time_range_util",
"@kbn/observability-logs-explorer-plugin": "link:x-pack/plugins/observability_solution/observability_logs_explorer",
"@kbn/observability-logs-overview": "link:x-pack/packages/observability/logs_overview",
"@kbn/observability-onboarding-plugin": "link:x-pack/plugins/observability_solution/observability_onboarding",
"@kbn/observability-plugin": "link:x-pack/plugins/observability_solution/observability",
"@kbn/observability-shared-plugin": "link:x-pack/plugins/observability_solution/observability_shared",
Expand Down Expand Up @@ -1050,6 +1052,7 @@
"@turf/helpers": "6.0.1",
"@turf/length": "^6.0.2",
"@xstate/react": "^3.2.2",
"@xstate5/react": "npm:@xstate/react@^4.1.2",
"adm-zip": "^0.5.9",
"ai": "^2.2.33",
"ajv": "^8.12.0",
Expand Down Expand Up @@ -1283,6 +1286,7 @@
"whatwg-fetch": "^3.0.0",
"xml2js": "^0.5.0",
"xstate": "^4.38.2",
"xstate5": "npm:xstate@^5.18.1",
"xterm": "^5.1.0",
"yauzl": "^2.10.0",
"yazl": "^2.5.1",
Expand All @@ -1304,6 +1308,7 @@
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-transform-class-properties": "^7.24.7",
"@babel/plugin-transform-logical-assignment-operators": "^7.24.7",
"@babel/plugin-transform-numeric-separator": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-env": "^7.24.7",
Expand Down
12 changes: 12 additions & 0 deletions packages/kbn-apm-synthtrace-client/src/lib/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export type ObjectEntry<T> = [keyof T, T[keyof T]];

export type Fields<TMeta extends Record<string, any> | undefined = undefined> = {
'@timestamp'?: number;
} & (TMeta extends undefined ? {} : Partial<{ meta: TMeta }>);
Expand All @@ -27,4 +29,14 @@ export class Entity<TFields extends Fields> {

return this;
}

overrides(overrides: Partial<TFields>) {
const overrideEntries = Object.entries(overrides) as Array<ObjectEntry<TFields>>;

overrideEntries.forEach(([fieldName, value]) => {
this.fields[fieldName] = value;
});

return this;
}
}
74 changes: 74 additions & 0 deletions packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { castArray } from 'lodash';
import { SynthtraceGenerator } from '../types';
import { Fields } from './entity';
import { Serializable } from './serializable';

export class GaussianEvents<TFields extends Fields = Fields> {
constructor(
private readonly from: Date,
private readonly to: Date,
private readonly mean: Date,
private readonly width: number,
private readonly totalPoints: number
) {}

*generator<TGeneratedFields extends Fields = TFields>(
map: (
timestamp: number,
index: number
) => Serializable<TGeneratedFields> | Array<Serializable<TGeneratedFields>>
): SynthtraceGenerator<TGeneratedFields> {
if (this.totalPoints <= 0) {
return;
}

const startTime = this.from.getTime();
const endTime = this.to.getTime();
const meanTime = this.mean.getTime();
const densityInterval = 1 / (this.totalPoints - 1);

for (let eventIndex = 0; eventIndex < this.totalPoints; eventIndex++) {
const quantile = eventIndex * densityInterval;

const standardScore = Math.sqrt(2) * inverseError(2 * quantile - 1);
const timestamp = Math.round(meanTime + standardScore * this.width);

if (timestamp >= startTime && timestamp <= endTime) {
yield* this.generateEvents(timestamp, eventIndex, map);
}
}
}

private *generateEvents<TGeneratedFields extends Fields = TFields>(
timestamp: number,
eventIndex: number,
map: (
timestamp: number,
index: number
) => Serializable<TGeneratedFields> | Array<Serializable<TGeneratedFields>>
): Generator<Serializable<TGeneratedFields>> {
const events = castArray(map(timestamp, eventIndex));
for (const event of events) {
yield event;
}
}
}

function inverseError(x: number): number {
const a = 0.147;
const sign = x < 0 ? -1 : 1;

const part1 = 2 / (Math.PI * a) + Math.log(1 - x * x) / 2;
const part2 = Math.log(1 - x * x) / a;

return sign * Math.sqrt(Math.sqrt(part1 * part1 - part2) - part1);
}
10 changes: 9 additions & 1 deletion packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface HostDocument extends Fields {
'cloud.provider'?: string;
}

class Host extends Entity<HostDocument> {
export class Host extends Entity<HostDocument> {
cpu({ cpuTotalValue }: { cpuTotalValue?: number } = {}) {
return new HostMetrics({
...this.fields,
Expand Down Expand Up @@ -175,3 +175,11 @@ export function host(name: string): Host {
'cloud.provider': 'gcp',
});
}

export function minimalHost(name: string): Host {
return new Host({
'agent.id': 'synthtrace',
'host.hostname': name,
'host.name': name,
});
}
3 changes: 2 additions & 1 deletion packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { dockerContainer, DockerContainerMetricsDocument } from './docker_container';
import { host, HostMetricsDocument } from './host';
import { host, HostMetricsDocument, minimalHost } from './host';
import { k8sContainer, K8sContainerMetricsDocument } from './k8s_container';
import { pod, PodMetricsDocument } from './pod';
import { awsRds, AWSRdsMetricsDocument } from './aws/rds';
Expand All @@ -24,6 +24,7 @@ export type InfraDocument =

export const infra = {
host,
minimalHost,
pod,
dockerContainer,
k8sContainer,
Expand Down
18 changes: 15 additions & 3 deletions packages/kbn-apm-synthtrace-client/src/lib/interval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ interface IntervalOptions {
rate?: number;
}

interface StepDetails {
stepMilliseconds: number;
}

export class Interval<TFields extends Fields = Fields> {
private readonly intervalAmount: number;
private readonly intervalUnit: unitOfTime.DurationConstructor;
Expand All @@ -46,12 +50,16 @@ export class Interval<TFields extends Fields = Fields> {
this._rate = options.rate || 1;
}

private getIntervalMilliseconds(): number {
return moment.duration(this.intervalAmount, this.intervalUnit).asMilliseconds();
}

private getTimestamps() {
const from = this.options.from.getTime();
const to = this.options.to.getTime();

let time: number = from;
const diff = moment.duration(this.intervalAmount, this.intervalUnit).asMilliseconds();
const diff = this.getIntervalMilliseconds();

const timestamps: number[] = [];

Expand All @@ -68,15 +76,19 @@ export class Interval<TFields extends Fields = Fields> {
*generator<TGeneratedFields extends Fields = TFields>(
map: (
timestamp: number,
index: number
index: number,
stepDetails: StepDetails
) => Serializable<TGeneratedFields> | Array<Serializable<TGeneratedFields>>
): SynthtraceGenerator<TGeneratedFields> {
const timestamps = this.getTimestamps();
const stepDetails: StepDetails = {
stepMilliseconds: this.getIntervalMilliseconds(),
};

let index = 0;

for (const timestamp of timestamps) {
const events = castArray(map(timestamp, index));
const events = castArray(map(timestamp, index, stepDetails));
index++;
for (const event of events) {
yield event;
Expand Down
21 changes: 21 additions & 0 deletions packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export type LogDocument = Fields &
'event.duration': number;
'event.start': Date;
'event.end': Date;
labels?: Record<string, string>;
test_field: string | string[];
date: Date;
severity: string;
Expand Down Expand Up @@ -156,6 +157,26 @@ function create(logsOptions: LogsOptions = defaultLogsOptions): Log {
).dataset('synth');
}

function createMinimal({
dataset = 'synth',
namespace = 'default',
}: {
dataset?: string;
namespace?: string;
} = {}): Log {
return new Log(
{
'input.type': 'logs',
'data_stream.namespace': namespace,
'data_stream.type': 'logs',
'data_stream.dataset': dataset,
'event.dataset': dataset,
},
{ isLogsDb: false }
);
}

export const log = {
create,
createMinimal,
};
53 changes: 53 additions & 0 deletions packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { PoissonEvents } from './poisson_events';
import { Serializable } from './serializable';

describe('poisson events', () => {
it('generates events within the given time range', () => {
const poissonEvents = new PoissonEvents(new Date(1000), new Date(2000), 10);

const events = Array.from(
poissonEvents.generator((timestamp) => new Serializable({ '@timestamp': timestamp }))
);

expect(events.length).toBeGreaterThanOrEqual(1);

for (const event of events) {
expect(event.fields['@timestamp']).toBeGreaterThanOrEqual(1000);
expect(event.fields['@timestamp']).toBeLessThanOrEqual(2000);
}
});

it('generates at least one event if the rate is greater than 0', () => {
const poissonEvents = new PoissonEvents(new Date(1000), new Date(2000), 1);

const events = Array.from(
poissonEvents.generator((timestamp) => new Serializable({ '@timestamp': timestamp }))
);

expect(events.length).toBeGreaterThanOrEqual(1);

for (const event of events) {
expect(event.fields['@timestamp']).toBeGreaterThanOrEqual(1000);
expect(event.fields['@timestamp']).toBeLessThanOrEqual(2000);
}
});

it('generates no event if the rate is 0', () => {
const poissonEvents = new PoissonEvents(new Date(1000), new Date(2000), 0);

const events = Array.from(
poissonEvents.generator((timestamp) => new Serializable({ '@timestamp': timestamp }))
);

expect(events.length).toBe(0);
});
});
Loading