Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
151 changes: 149 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 @@ -151,7 +151,6 @@ const hawk = new HawkCatcher({

or pass it any moment after Hawk Catcher was instantiated:


```js
import Vue from 'vue';

Expand All @@ -161,3 +160,151 @@ 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

Hawk JavaScript Catcher also provides performance monitoring capabilities. You can track transactions and spans to measure the performance of your application.

### Basic Usage

```javascript
const hawk = new Catcher('your-integration-token');

// Start a transaction
const transaction = hawk.startTransaction('page-load', {
route: '/dashboard',
method: 'GET'
});

// Create a span for measuring API call
const apiSpan = hawk.startSpan(transaction.id, 'api-request', {
url: '/api/users',
method: 'GET'
});

// Do some API work...

// Finish the span when API call is done
hawk.finishSpan(apiSpan.id);

// Create another span for data processing
const processSpan = hawk.startSpan(transaction.id, 'process-data');

// Do processing work...

// Finish the processing span
hawk.finishSpan(processSpan.id);

// Finish the transaction
hawk.finishTransaction(transaction.id);
```

### API Reference

#### startTransaction(name: string, tags?: Record<string, string>): Transaction

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

- `name`: Name of the transaction
- `tags`: Optional key-value pairs for additional transaction data

#### startSpan(transactionId: string, name: string, metadata?: Record<string, any>): Span

Creates a new span within a transaction. Spans represent smaller units of work within a transaction.

- `transactionId`: ID of the parent transaction
- `name`: Name of the span
- `metadata`: Optional metadata for the span

#### finishSpan(spanId: string): void

Finishes a span and calculates its duration.

- `spanId`: ID of the span to finish

#### finishTransaction(transactionId: string): void

Finishes a transaction, calculates its duration, and sends the performance data to Hawk.

- `transactionId`: ID of the transaction to finish

### Data Model

#### Transaction
```typescript
interface Transaction {
id: string;
traceId: string;
name: string;
startTime: number;
endTime?: number;
duration?: number;
tags: Record<string, string>;
spans: Span[];
}
```

#### Span
```typescript
interface Span {
id: string;
transactionId: string;
name: string;
startTime: number;
endTime?: number;
duration?: number;
metadata?: Record<string, any>;
}
```

### Examples

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

const hawk = new Catcher('your-integration-token');

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

next();

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

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

const apiSpan = hawk.startSpan(transaction.id, 'api-call', {
url: '/api/users',
method: 'GET'
});

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

hawk.finishSpan(apiSpan.id);

const processSpan = hawk.startSpan(transaction.id, 'process-data');
// Process data...
hawk.finishSpan(processSpan.id);

return data;
} finally {
hawk.finishTransaction(transaction.id);
}
}
```
94 changes: 92 additions & 2 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,60 @@
font-size: 15px;
color:inherit;
}

.performance-demo {
border-radius: 4px;
}

.performance-demo button {
padding: 8px 16px;
padding-right: 32px;
background: #4979E4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
position: relative;
min-width: 200px;
text-align: left;
}

.performance-demo button:hover {
background: #4869d2;
}

.performance-demo button[disabled] {
background: #4979E4;
opacity: 0.7;
cursor: not-allowed;
}

.performance-demo button .spinner {
display: none;
width: 10px;
height: 10px;
border: 2px solid #fff;
border-bottom-color: transparent;
border-radius: 50%;
animation: rotation 1s linear infinite;
position: absolute;
right: 10px;
top: 50%;
margin-top: -5px;
}

.performance-demo button[disabled] .spinner {
display: block;
}

@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
Expand Down Expand Up @@ -152,7 +206,17 @@ <h2>Test console catcher</h2>
<br><br>
<button id="btn-console-test">Make</button>
</section>
<script src="https://unpkg.com/vue@2"></script>
<section>
<h2>Performance Monitoring Demo</h2>
<div class="performance-demo">
<button id="simulateComplexOperation">
Simulate Complex Operation
<span class="spinner"></span>
</button>
</div>
</section>
<!-- <script src="https://unpkg.com/vue@2"></script> -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<section>
<h2>Test Vue integration: $root</h2>
<div id="vue-app-1">
Expand Down Expand Up @@ -196,7 +260,7 @@ <h2>Test Vue integration: &lt;test-component&gt;</h2>
el: tag,
instance: new Editor(),
classProto: Editor,
longText: 'Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.',
longText: 'Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.Upvoting this, given it\'s simplicity. In my use case I need to check all the attributes in an object for dodgy values-NaNs, nulls, undefined (they were points on a graph and these values prevented the graph from drawing). To get the value instead of the name, in the loop you would just do obj[someVariable]. Perhaps the reason it was downvoted so much is because it is not recursive. So this would not be an adequate solution if you have a highly structured object.',
}
},
props: {
Expand Down Expand Up @@ -236,5 +300,31 @@ <h2>Test Vue integration: &lt;test-component&gt;</h2>
},
})
</script>
<script>
// Simulate complex operation with nested spans
document.getElementById('simulateComplexOperation').addEventListener('click', () => {
const button = document.getElementById('simulateComplexOperation');
button.disabled = true;

const transaction = window.hawk.startTransaction('complex-operation', {
type: 'background'
});

const step1 = window.hawk.startSpan(transaction.id, 'step-1');
setTimeout(() => {
window.hawk.finishSpan(step1.id);

const step2 = window.hawk.startSpan(transaction.id, 'step-2');
// Simulate unfinished span

const step3 = window.hawk.startSpan(transaction.id, 'step-3');
setTimeout(() => {
window.hawk.finishSpan(step3.id);
window.hawk.finishTransaction(transaction.id);
button.disabled = false;
}, 300);
}, 400);
});
</script>
</body>
</html>
66 changes: 66 additions & 0 deletions src/catcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@
import { EventRejectedError } from './errors';
import type { HawkJavaScriptEvent } from './types';
import { isErrorProcessed, markErrorAsProcessed } from './utils/event';
import PerformanceMonitoring from './modules/performance';
import type { Transaction } from './types/transaction';
import type { Span } from './types/span';

/**
* Allow to use global VERSION, that will be overwritten by Webpack
*/
declare const VERSION: string;


/**
* Hawk JavaScript Catcher
* Module for errors and exceptions tracking
Expand Down Expand Up @@ -91,6 +95,12 @@
*/
private readonly disableVueErrorHandler: boolean = false;


/**
* Performance monitoring instance
*/
private readonly performance: PerformanceMonitoring;

/**
* Catcher constructor
*
Expand Down Expand Up @@ -145,6 +155,16 @@
if (settings.vue) {
this.connectVue(settings.vue);
}

/**
* Init performance monitoring
*/
this.performance = new PerformanceMonitoring(
this.transport,
this.token,
this.version,
this.debug
);
}

/**
Expand Down Expand Up @@ -542,4 +562,50 @@
private appendIntegrationAddons(errorFormatted: CatcherMessage, integrationAddons: JavaScriptCatcherIntegrations): void {
Object.assign(errorFormatted.payload.addons, integrationAddons);
}

/**
* Starts a new transaction
*
* @param name - Name of the transaction (e.g., 'page-load', 'api-request')
* @param tags - Key-value pairs for additional transaction data
*/
public startTransaction(name: string, tags: Record<string, string> = {}): Transaction {

Check failure on line 572 in src/catcher.ts

View workflow job for this annotation

GitHub Actions / lint

Member startTransaction should be declared before all private instance method definitions
return this.performance.startTransaction(name, tags);
}

/**
* Starts a new span within a transaction
*
* @param transactionId - ID of the parent transaction this span belongs to
* @param name - Name of the span (e.g., 'db-query', 'http-request')
* @param metadata - Additional context data for the span
*/
public startSpan(transactionId: string, name: string, metadata?: Record<string, any>): Span {

Check failure on line 583 in src/catcher.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check failure on line 583 in src/catcher.ts

View workflow job for this annotation

GitHub Actions / lint

Member startSpan should be declared before all private instance method definitions
return this.performance.startSpan(transactionId, name, metadata);
}

/**
* Finishes a span
*
* @param spanId - ID of the span to finish
*/
public finishSpan(spanId: string): void {

Check failure on line 592 in src/catcher.ts

View workflow job for this annotation

GitHub Actions / lint

Member finishSpan should be declared before all private instance method definitions
this.performance.finishSpan(spanId);
}

/**
* Finishes a transaction
*
* @param transactionId - ID of the transaction to finish
*/
public finishTransaction(transactionId: string): void {

Check failure on line 601 in src/catcher.ts

View workflow job for this annotation

GitHub Actions / lint

Member finishTransaction should be declared before all private instance method definitions
this.performance.finishTransaction(transactionId);
}

/**
* Clean up resources
*/
public destroy(): void {

Check failure on line 608 in src/catcher.ts

View workflow job for this annotation

GitHub Actions / lint

Member destroy should be declared before all private instance method definitions
this.performance.destroy();
}
}
Loading