TTC (Time‑To‑Completion)
Purpose: Provide a clear, consistent, and concise reference for anyone who needs to measure, instrument, report, and interpret TTC in a product.
1. Overview
What is TTC?
The elapsed time from the moment a user initiates a business‑level action (e.g., “Add to Cart”, “Submit Order”, “Start Upload”) until the system reports that the action is completed successfully.
Why it matters?
Directly correlates with perceived performance and conversion.
Faster TTC → higher satisfaction & revenue.
What can be measured?
Front‑end user‑journey events only. Server‑side performance.mark‑type timestamps are excluded because they do not map to a user‑visible milestone. |
| Term | Definition |
|---|---|
| Start point | The UI event that indicates the user has begun the flow (e.g., button click, form submit). |
| End point | The UI event that signals the operation finished (e.g., success toast, navigation to a confirmation page, API‑success callback). |
| TTC value | endTimestamp - startTimestamp measured in milliseconds. |
2. TTC Measure Instance
2.1 TTC Singleton Object
In TTC Measure instance, we will introduce a singleton object that will be used on each page. This singleton will store any events that are invoked in the event marking function.
| Field | Type | Notes | |
|---|---|---|---|
<event_name> | ITTCEventEntry | Event name that's already registered in datadog metrics tag. | |
| startTime | number/undefined | Value of performance.mark() or performance.now(), default unset | |
| endTime | number/undefined | Value of performance.mark() or performance.now(), default unset | |
| status | string/undefined | Value to determine if the event is successful, default unset. Possible values: 'success', 'timeout', 'failure' | |
| config | ITTCEventConfig | Config for related event |
Sample filled object:
const ttcEvents = {
// #### Sample completed event entry
"card_form_completion": {
startTime: 2112040.099999994,
endTime: 2129180.099999994
status: 'success',
config: {
sendToDDLogs: true
}
},
// #### Sample ongoing event entry
"payment_page_to_click_paynow": {
startTime: 2112040.099999994,
endTime: undefined,
status: undefined,
config: {}
}
// #### Sample event entry that has not started
"method_selection": {
startTime: undefined,
endTime: undefined,
status: undefined,
config: {
onlyRecordOnce: true
}
}
}
2.2 TTC Config
As each event may behave differently, we'll allow the product team to define each event's behavior.
By default:
- Events can be recorded multiple times
- If event has started, calling markTTCEventStart() will be ignored until markTTCEventEnd() is called
- Marking the start of an event will not override the previous start time
- Events will not be sent to DD logs
- Both successful and failed events will be sent to DD logs if sendToDDLogs is true
- Timeout is not set
| Config property | Type | Notes |
|---|---|---|
| onlyRecordOnce | boolean | Whether to only record the event once, default false. If true, subsequent calls to markTTCEventStart() and markTTCEventEnd() for the same event will be ignored |
| overrideStartTime | boolean | Whether to override the mark start of an event, default false. If true, calling markTTCEventStart() will override the previous start time and clear up existing performance marks of start events |
| preserveMarksAndMeasuresAfterUpload | boolean | Whether to preserve marks and measures after upload, default false. If true, performance marks and measures related to the event will not be cleared after uploading the event data. Useful for event visualizations in devTool |
| sendToDDLogs | boolean | Whether to send the event to DD logs, default false |
| sendFailedOnlyToDDLogs | boolean | Whether to send only failed events to DD logs, default false |
| timeout | number | Optional timeout for the event (in ms), default unset. If the event is not ended within the timeout period after it is started, markTTCEventEnd will be automatically called with status = 'timeout'. |
2.3 TTC Hooks & Instance Creation
To detect if a page wants to enable TTC measurement, the intended page only needs to import useTTCMeasure() hooks.
- On the product main component, use
useTTCMeasure()hooks with ttcConfig. Can refer to product usage example on Section 4.2. - When ttcConfig and productDomain (from
getPublicRuntimeConfig) is defined, it will trigger TTC Measure instance initialization with productDomain, ttcConfig, and trackMetrics callback. - After the initialization, the TTC Measure instance will be ready to use on the page.
2.4. Core functions
/**
* React hook to initialize TTC measurement.
*
* This hook should be used in the index/main component of the page.
* It also handles cleanup of TTCMeasure instance on unmount or route change.
* As TTC is a client-side only measurement, this hook does nothing on server-side rendering.
*
* @param ttcConfig - TTC configuration for the current application
* @param onEventUpload - optional callback function that will be called when an event is uploaded
*/
export function useTTCMeasure(
ttcConfig: ITTCConfig,
onEventUpload?: (data: ITTCEventCallbackData) => void
): void;
/**
* Mark the start of an event to be tracked
*
* @param eventName - name of the event to be tracked
* @param data - optional data to be associated with the event (for Datadog logging, supports rich object)
* @param tags - optional tags to be associated with the event (for Datadog metrics, must be registered in BEI repo)
*/
export function markTTCEventStart<T extends string>(
eventName: T,
data: Record<string, any> = {},
tags: Record<string, string> = {}
): void;
/**
* Mark the end of an event to be tracked
*
* @param eventName - name of the event to be tracked
* @param status - status of the event, possible values: 'success', 'failure', 'timeout'. Default 'success'
* @param data - optional data to be associated with the event (for Datadog logging, supports rich object)
* @param tags - optional tags to be associated with the event (for Datadog metrics, must be registered in BEI repo)
*/
export function markTTCEventEnd<T extends string>(
eventName: T,
status: ITTCEventEntry['status'] = 'success',
data: Record<string, any> = {},
tags: Record<string, string> = {}
): void;
/**
* Set common tags for TTC measurement, the tag will be applied for all TTC events in the current page.
*
* @param tags - common tags to be set, MUST be registered in BEI repo
*/
export function setTTCCommonTags(tags: Record<string, string>);
2.5. Recommended Placement
| Flow | markTTCEventStart location | markTTCEventEnd location |
|---|---|---|
| Login | onSubmit of login form (immediately before API call) | Success callback of login request (or error handler) |
| Payment | Click on “Pay” button | Navigation to guideline page (or other callbacks) |
| File Upload | onChange of file input (after file is selected) | onUploadSuccess / onUploadError callback |
2.6. Caveats
- Do not use
markTTCEventStarton the server side as a TTC marker – it does not reflect the user journey on devTools. - Do not start/stop TTC inside generic utilities that may be reused across unrelated flows; keep the start/end pair tight to the UI action.
- Do not set unregistered tags during marking events or setting common tags. Tags sent must be available in BEI repo first before use. See Section 3.1.
3. Reporting
3.1 TTC Datadog Metrics
Every time TTC Measure instance is initialized, it will read getPublicRuntimeConfig().productDomain value to determine product owner of importing page.
This productDomain value will then be used as datadog metrics name.
Why don't we use centralized metric like TTI?
- As TTC is highly coupled with product needs, the tags needed may differ per domain.
- If we use centralized metrics, there may be some tags that actually are not used, but all domains must track anyway, since the tags are shared across all domains.
- Cost-wise, having multiple domain metrics and centralized metrics will be similar as the pricing is pay-per-use.
Built-in DD Metrics Logging
| Property | Value Example |
|---|---|
| Metric Name | web.[domain].performance.page.ttc |
| country | 'id', 'au' |
| interface | 'desktop', 'mobile' |
| locale | 'en-id', 'ja-jp' |
| is_bot | 'true', 'false', common tag filtered based on aws waf known_bot header |
| event_name | 'form_completion', 'submit_payment', 'item_selection' |
| status | 'success', 'timeout', 'failure' |
Important Notes
- Metric name must be registred to BEI repo before use if your domain hasn't had one yet. See DD metrics registration guide.
Country,interface,is_bot, andlocalewill be always be sentevent_nameandstatuswill be sent for all TTC events- Other tags specific for your product usage can be added along with marking events, or set using
setTTCCommonTags()function.
3.2 TTC Datadog Logs
Datadog logging is optional for TTC. Product team can setup the config if they would like to include datadog logging for each events.
| Property | Entry | Value Example/Format |
|---|---|---|
| Name | @name | web.[domain].performance.page.tti |
| service | @service | webfpr-desktop, webpla-mobile |
| page | @meta.pathname | /flight/fullsearch, /car-rental |
| locale | @meta.route_prefix | en-id, en-en |
| app_version | @props.app_version | release_webgtr_20251112-e17b158559 |
| data.end | @props.data.end | Data sent when marking end |
| data.start | @props.data.start | Data sent when marking start |
| duration | @props.duration | 2000, 52223 |
| eventName | @props.eventName | 'form_completion', 'submit_payment', 'item_selection' |
| endTime | @props.endTime | Performance marking time for the end of the event |
| startTime | @props.startTime | Performance marking time for the start of the event |
| status | @props.status | 'success', 'failure', 'timeout' |
| message | @props.message | TTC measure message |
4. Product Usage
4.1 Datadog Metrics Registration
-
[First time only] Make changes to the BEI repo to register new metrics.
Register:- Metrics owner registration
- Metrics name and tags -> Format:
web.[domain].performance.page.ttc - Tag group & possible values -> This determines which tags you can send during TTI marking events.
See sample PR: https://github.com/traveloka/bei-observability-platform/pull/410
This step is optional, only for first time integration if you don't have
web.[domain].performance.page.ttcmetrics in Datadog yet. -
Make changes to BEI repo to add eventNames you'd like to track.
By registering eventName, your metrics can filter and create SLOs by the eventName value.This step is mandatory for any new event name registration.
-
Request review to techops-platform channel and provide tag series combination estimation.
Sample tag series calculation:- Interface: 5 interfaces- Event_name: 10 events- Currencies: 29 currenciesIn total: 5 x 10 x 29 = 1450 series for payment web TTC
4.2 Enable TTC on related page
Add useTTCMeasure to the main/top component of the page. This will initialize TTC Measure instance on the client.
Along with the useTTCMeasure, we will pass a TTC config object which will determine how we treat each of the events. For an event that uses default config, you can set an empty object as the value.
// packages/payment/app-desktop/page/search/index.tsx
import { useTTCMeasure, ITTCConfig } from '@traveloka/core';
// Define event names and configs related to each event
const ttcMeasureConfig: ITTCConfig = {
search_form_input: { overrideStartTime: true, sendFailedOnlyToDDLogs: true },
page_load_to_first_item_click: { onlyRecordOnce: true, sendToDDLogs: true },
item_selection: {
sendToDDLogs: true,
preserveMarksAndMeasuresAfterUpload: true,
timeout: 30000,
},
card_form_completion: {}, // default config,
};
const Page = () => {
// Construct TTC measure instance for this page
useTTCMeasure(ttcMeasureConfig);
return <></>;
};
4.3 Invoke event start & end markers
After the TTC instance was created, the markTTCEventStart() and markTTCEventEnd() function is ready to use according to the intended product flow.
// container/CreditCardForm.tsx
import { markTTCEventStart, markTTCEventEnd } from '@traveloka/core';
export function CreditCardForm() {
const [isFormLoaded, setFormLoaded] = setState(false)
// Mark TTC event start when credit card form loaded
React.useEffect(() => {
if (isFormLoaded) {
markTTCEventStart('card_form_completion')
}
}, [isFormLoaded])
// Mark TTC event end when form validated
function validateForm() {
const isValid = validate()
if (!isValid) return;
markTTCEventEnd('card_form_completion', true, { trigger: 'GPO' }, { invoice_type: 'standard'})
}
return (...)
}
4.4 [Optional] Set common DD metrics tags for all events
If you need to send a custom tag for all events within your page, instead of passing it to all of the event markers, you can utilize the setTTCCommonTags() function.
Example:
import { setTTCCommonTags } from '@traveloka/core';
export function CreditCardForm() {
const {currency} = useAPIFetcher()
// Mark TTC common tags when related data source has loaded
React.useEffect(() => {
setTTCCommonTags({
currency,
})
}, [currency, pageSource])
return (...)
}
4.5 Sample reporting
When markTTCEventEnd() is invoked, it will directly send the ttc tracking to datadog.
Based on the example above, tracking sent will be:
- Datadog metrics:
web.payment.performance.page.ttc{"value": 2000,"tags": {"event_name": "card_form_completion","invoice_type": "standard","currency": "IDR","status": "success"}} - Datadog log:
service:webpay-desktop{"name": "web.payment.performance.page.ttc","props": {"app_version": "release_webpay_asdfghjk","duration": 2000,"eventName": "card_form_completion","message": "[TTC Measure] card_form_completion success with duration: 2000ms","data": {"end": {"trigger": "GPO"},"start": {}},"startTime": 1000,"endTime": 3000,"status": "success"}}
5. Common FAQs
| Question | Answer |
|---|---|
Can I reuse the same event_name for different pages? | Yes, we differentiate event_name ownership by metric name web.<domain>.performance.page.ttc. So multiple event_names can be triggered anywhere as long as it's still registered under the same metric name |
| What if the end point is a third‑party redirect? | Emit the markTTCEventEnd before leaving the page (e.g., in the click handler that triggers the redirect). |
| Should I include network latency? | Yes – TTC is end‑to‑end from the user’s perspective, so network time is part of the measurement. |
How to handle multiple concurrent TTCs with the same event_name? | There can only be 1 event happening for 1 event_name at once. When it has ended, you can start a new event with the same event name. |