Each process holds one integer, usually starting at 0. Three rules drive every update.
First, before a process performs a local event, it increments its counter by 1. Second, when it sends a message, it increments its counter and stamps that value onto the message. Third, when it receives a message carrying timestamp T, it sets its counter to max of its own counter and T, then adds 1. That max step is what keeps a slow receiver from issuing a timestamp smaller than the sender it just heard from.
To get a total order with no ties, you break equal timestamps by a fixed tiebreaker such as the process id. So the full comparison is the pair of counter then process id. That turns a partial order into a total order, which is what algorithms like Lamport's original mutual exclusion need.