Skip to content

feat(perf): Add performance measuring api #117

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

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c022264
Implement perf events
TatianaFomina Mar 15, 2025
0bc0093
feat(performance): enhance performance monitoring with batch sending …
TatianaFomina Mar 15, 2025
767592b
Lint
TatianaFomina Mar 15, 2025
ab1ab01
Upd
TatianaFomina Mar 15, 2025
751a048
Upd
TatianaFomina Mar 15, 2025
5daeb4e
Upd api
TatianaFomina Mar 15, 2025
b5d71c4
Remove traceId
TatianaFomina Mar 15, 2025
d708ebb
feat(performance): add performance monitoring
TatianaFomina Mar 15, 2025
1c7a123
feat(performance): enhance performance monitoring configuration and s…
TatianaFomina Mar 15, 2025
2cabdaa
feat(performance): add performance monitoring settings demo with tran…
TatianaFomina Mar 15, 2025
a0d56bb
refactor(performance): simplify transaction queuing and remove unused…
TatianaFomina Mar 15, 2025
933f7f9
refactor(performance): update PerformancePayload interface to include…
TatianaFomina Mar 15, 2025
6c544bd
Lint
TatianaFomina Mar 15, 2025
8309ae3
Fix
TatianaFomina Mar 15, 2025
d611f1d
Review
TatianaFomina Mar 15, 2025
2459f5d
feat(performance): introduce batch sending configuration and enhance …
TatianaFomina Mar 15, 2025
c1639ca
feat(performance): add batch interval configuration and update UI for…
TatianaFomina Mar 15, 2025
c8b37bc
style: clean up code formatting and remove unnecessary whitespace in …
TatianaFomina Mar 15, 2025
1e57084
chore(performance): remove debugger statement from startTransaction m…
TatianaFomina Mar 15, 2025
8e8d0f7
Split
TatianaFomina Mar 16, 2025
efa12f9
Review
TatianaFomina Mar 16, 2025
677fdaf
feat(performance): update HawkCatcher initialization and enhance perf…
TatianaFomina Mar 16, 2025
1f1e758
fix(performance): correct transaction length reference and update bat…
TatianaFomina Mar 16, 2025
93fd56c
Add doc
TatianaFomina Mar 16, 2025
846e864
Update performance-monitoring.md
neSpecc Mar 16, 2025
e510e2a
Update performance-monitoring.md
neSpecc Mar 16, 2025
cefde4d
Update performance-monitoring.md
neSpecc Mar 16, 2025
e6885ca
Update performance-monitoring.md
neSpecc Mar 16, 2025
366ca89
Update respectively to docs
TatianaFomina Mar 16, 2025
936da97
Upd
TatianaFomina Mar 16, 2025
6ffcf16
Update readme
TatianaFomina Mar 16, 2025
e007868
Refactor HawkCatcher initialization in monitoring.html to use a const…
TatianaFomina Mar 16, 2025
bbc9cea
Merge branch 'master' into feat/perf
TatianaFomina Mar 16, 2025
f11bdd1
Enhance performance monitoring by adding critical duration threshold …
TatianaFomina Mar 16, 2025
c4f0094
Refactor performance monitoring code by adding missing commas for con…
TatianaFomina Mar 16, 2025
e5a32ca
Add retries
TatianaFomina Mar 16, 2025
17c44f5
Enhance performance monitoring by adding detailed documentation for S…
TatianaFomina Mar 16, 2025
61649a9
Refactor consoleCatcher and performance monitoring code for improved …
TatianaFomina Mar 16, 2025
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
155 changes: 153 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Then import `@hawk.so/javascript` module to your code.

```js
import HawkCatcher from '@hawk.so/javascript';
````
```

### Load from CDN

Expand Down Expand Up @@ -74,6 +74,7 @@ Initialization settings:
| `disableGlobalErrorsHandling` | boolean | optional | Do not initialize global errors handling |
| `disableVueErrorHandler` | boolean | optional | Do not initialize Vue errors handling |
| `beforeSend` | function(event) => event | optional | This Method allows you to filter any data you don't want sending to Hawk |
| `performance` | boolean\|object | optional | Performance monitoring settings. When object, accepts: <br> - `sampleRate`: Sample rate (0.0 to 1.0, default: 1.0) <br> - `thresholdMs`: Minimum duration threshold in ms (default: 20) <br> - `criticalDurationThresholdMs`: Duration threshold for critical transactions in ms (default: 500) <br> - `batchInterval`: Batch send interval in ms (default: 3000) |

Other available [initial settings](types/hawk-initial-settings.d.ts) are described at the type definition.

Expand Down Expand Up @@ -151,7 +152,6 @@ const hawk = new HawkCatcher({

or pass it any moment after Hawk Catcher was instantiated:


```js
import Vue from 'vue';

Expand All @@ -161,3 +161,154 @@ const hawk = new HawkCatcher({

hawk.connectVue(Vue)
```

## Performance Monitoring

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • explain what is Transaction and Span.
  • move API Reference on the first place
  • optimization explanation is a technical detail, so it should be placed after usage description

The SDK can monitor performance of your application by tracking transactions and spans.

### Transaction Batching and Aggregation

Transactions are collected, aggregated, and sent in batches to reduce network overhead and provide statistical insights:

- Transactions with the same name are grouped together
- Statistical metrics are calculated (p50, p95, max durations)
- Spans are aggregated across transactions
- Failure rates are tracked for both transactions and spans

You can configure the batch interval using the `batchInterval` option:

```js
const hawk = new HawkCatcher({
token: 'INTEGRATION_TOKEN',
performance: {
batchInterval: 5000 // Send batches every 5 seconds
}
});
```

### Sampling and Filtering

You can configure what percentage of transactions should be sent to Hawk using the `sampleRate` option:

```typescript
const hawk = new HawkCatcher({
token: 'INTEGRATION_TOKEN',
performance: {
sampleRate: 0.2, // Sample 20% of transactions
thresholdMs: 50 // Only send transactions longer than 50ms
}
});
```

Transactions are automatically filtered based on:
- Duration threshold (transactions shorter than `thresholdMs` are ignored)
- Critical duration threshold (transactions longer than `criticalDurationThresholdMs` are always sent)
- Sample rate (random sampling based on `sampleRate`)
- Severity (critical transactions are always sent regardless of sampling)
- Status (failed transactions are always sent regardless of sampling)

### API Reference

#### startTransaction(name: string, severity?: 'default' | 'critical'): Transaction

Starts a new transaction. A transaction represents a high-level operation like a page load or an API call.

- `name`: Name of the transaction
- `severity`: Optional severity level. 'critical' transactions are always sent regardless of sampling.

#### Transaction Methods

```typescript
interface Transaction {
// Start a new span within this transaction
startSpan(name: string): Span;

// Finish the transaction with optional status
finish(status?: 'success' | 'failure'): void;
}
```

#### Span Methods

```typescript
interface Span {
// Finish the span with optional status
finish(status?: 'success' | 'failure'): void;
}
```

### Examples

#### Measuring Route Changes in Vue.js
```javascript
import { HawkCatcher } from '@hawk.so/javascript';
import Vue from 'vue';
import Router from 'vue-router';

const hawk = new HawkCatcher({
token: 'INTEGRATION_TOKEN',
performance: true
});

router.beforeEach((to, from, next) => {
const transaction = hawk.startTransaction('route-change');

next();

// After route change is complete
Vue.nextTick(() => {
transaction.finish();
});
});
```

#### Measuring API Calls with Error Handling
```javascript
async function fetchUsers() {
const transaction = hawk.startTransaction('fetch-users');

const apiSpan = transaction.startSpan('api-call');

try {
const response = await fetch('/api/users');

if (!response.ok) {
apiSpan.finish('failure');
transaction.finish('failure');
return null;
}

const data = await response.json();
apiSpan.finish('success');

const processSpan = transaction.startSpan('process-data');
// Process data...
processSpan.finish();

transaction.finish('success');
return data;
} catch (error) {
apiSpan.finish('failure');
transaction.finish('failure');
throw error;
}
}
```

#### Critical Transactions
```javascript
function processPayment(paymentDetails) {
// Mark as critical to ensure it's always sent regardless of sampling
const transaction = hawk.startTransaction('payment-processing', 'critical');

try {
// Payment processing logic...

transaction.finish('success');
return true;
} catch (error) {
transaction.finish('failure');
throw error;
}
}
```
169 changes: 169 additions & 0 deletions docs/performance-monitoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Performance Monitoring

## Optimizations

Sending all transactions without filtering and aggregation would create an heavy load on the service. We must carefully balance data completeness with efficient storage and processing to ensure optimal performance monitoring.

### 1. Transaction Selection Criteria

#### Problem: Too Much Data in case of full sending

Sending every transaction without filtering creates several critical issues:

- Significantly increased server load, particularly during high traffic periods
- Large amounts of redundant data that provide minimal analytical value
- "Infinite loops" in client code may generate endless transactions.

#### Solution: Smart Sampling and Grouping

Instead of collecting every transaction, we focus on gathering a representative sample that provides meaningful insights while minimizing data volume.

### 2. Data Flow Optimization Strategies
#### Optimization #1: Sampling (Sending Part of the Data Randomly)

To ensure we capture important but infrequent transactions:

– Slow transactions are always sent. Transaction is considered slow if its duration is greater than criticalDurationThresholdMs parameter.
- Errors (`status` == 'failure') are always sent.

See [Sampling](#sampling) for details.

#### Optimization #2: Aggregation of Identical Transactions Before Sending

Throttling + transaction batches → instead of 1000 separate messages, send 1 `AggregatedTransaction`.

##### Combine transactions with the same name (e.g., GET /api/users) and time window (e.g., 3 seconds).

Instead of 1000 transactions, send one with count = 1000 and average metrics.

##### How to choose the time?

- Store P50, P95, P100 (percentiles).
- Save min(startTime) and max(endTime) to see the interval boundaries and calculate Transactions Per Minute.

**What do we lose ?**
- The detail of each specific transaction.
- Exact startTime and endTime for each transaction.

**What do we gain?**
- A sharp reduction in load on the Collector and DB (10-100 times fewer records).
- All necessary metrics (P50, P95, P100, avg) remain.
- You can continue to build graphs and calculate metrics, but with less load.

See [Transaction Aggregation](#transaction-aggregation) for details on how transactions are aggregated.

#### Optimization #3: Filtering "Garbage"

Transactions with duration < `thresholdMs` will not be sent, as they are not critical.


## Data types

### Transaction
| Field | Type | Description |
|-------|------|-------------|
| id | string | Unique identifier of the transaction |
| severity | string | Type of transaction sampling ('default' or 'critical'). See [Sampling](#sampling) for details |
| name | string | Name of the transaction |
| startTime | number | Timestamp when transaction started |
| endTime | number | Timestamp when transaction ended |
| duration | number | Total duration of transaction in milliseconds |
| status | string | Status when transaction finished. 'success' (default) or 'failure'. See [Transaction Completion](#2-transaction-completion) |
| spans | Span[] | Array of [spans](#span) associated with this transaction |

### AggregatedTransaction
| Field | Type | Description |
|-------|------|-------------|
| aggregationId | string | Identifier of the aggregation |
| name | string | Name of the transaction |
| avgStartTime | number | Average timestamp when transaction started |
| minStartTime | number | Minimum timestamp when transaction started |
| maxEndTime | number | Maximum timestamp when transaction ended |
| p50duration | number | 50th percentile (median) duration of transaction in milliseconds |
| p95duration | number | 95th percentile duration of transaction in milliseconds |
| maxDuration | number | Maximum duration of transaction in milliseconds |
| count | number | how many transactions aggregated |
| failureRate | number | percentage of transactions with status 'failure' |
| aggregatedSpans | AggregatedSpan[] | List of spans in transactions |


### Span
| Field | Type | Description |
|-------|------|-------------|
| id | string | Unique identifier of the span |
| name | string | Name of the span |
| startTime | number | Timestamp when span started |
| endTime | number | Timestamp when span ended |
| duration | number | Total duration of span in milliseconds |
| status | string | Status when span finished. 'success' (default) or 'failure' |

### AggregatedSpan
See [Transaction Aggregation](#transaction-aggregation) for details on how spans are aggregated.

| Field | Type | Description |
|-------|------|-------------|
| aggregationId | string | Unique identifier of the span aggregation |
| name | string | Name of the span |
| minStartTime | number | Minimum timestamp when span started |
| maxEndTime | number | Maximum timestamp when span ended |
| p50duration | number | 50th percentile (median) duration of span in milliseconds |
| p95duration | number | 95th percentile duration of span in milliseconds |
| maxDuration | number | Maximum duration of span in milliseconds |
| failureRate | number | percentage of spans with status 'failure' |

## Transaction Lifecycle

### 1. Transaction Creation

When creating a transaction, you can specify its type:

- 'critical' - important transactions that are always sent to the server
- 'default' - regular transactions that go through the [sampling process](#sampling)

### 2. Transaction Completion

When completing a transaction:

1. A finish status is specified (`status`):
- 'success' (default) - successful completion
- 'failure' - completion with error (such transactions are always sent to the server)

2. The transaction duration is checked:
- If `thresholdMs` parameter is specified and the transaction duration is less than this value, the transaction is discarded
- Default `thresholdMs` is 20ms
- `status` "failure" has a priority over `thresholdMs`
- Otherwise, the transaction goes through the [sampling process](#sampling)

3. After successful sampling, the transaction is added to the list for sending

### 3. Sending Transactions

- When the first transaction is added to the list, a timer starts
- When the timer expires:
1. All collected transactions are [aggregated](#transaction-aggregation)
2. Aggregated data is sent to the server
3. The transaction list is cleared

## Sampling

- The probability of sending transactions is configured through the `performance.sampleRate` parameter (value from 0 to 1)
- Only transactions of type 'default' with finish status 'success' are subject to sampling
- Sampling process:
1. A random number between 0 and 1 is generated for each transaction
2. If the number is less than or equal to sampleRate, the transaction is sent

## Transaction Aggregation

1. [Transactions](#transaction) are grouped by name (name field)
2. For each group, statistical indicators are calculated:
- minStartTime - earliest start time
- maxEndTime - latest end time
- p50duration - median duration (50th percentile)
- p95duration - 95th percentile duration
- maxDuration - maximum duration

3. Based on this data, [AggregatedTransaction](#aggregatedtransaction) objects are created
4. For each aggregated transaction:
- [Spans](#span) are grouped by name
- Their own statistical indicators (see [AggregatedSpan](#aggregatedspan)) are calculated for each span group
- [AggregatedSpan](#aggregatedspan) objects are created
Loading