class ExecutingQuery {

    public QuerySnapshot snapshot()
    {
        // capture a consistent snapshot of the "live" state
        ExecutingQueryStatus status;
        long waitTimeNanos;
        long currentTimeNanos;
        long cpuTimeNanos;
        do
        {
            status = this.status; // read barrier, must be first
            waitTimeNanos = this.waitTimeNanos; // the reason for the retry loop: don't count the wait time twice
            cpuTimeNanos = cpuClock.cpuTimeNanos( threadExecutingTheQueryId );
            currentTimeNanos = clock.nanos(); // capture the time as close to the snapshot as possible
        }
        while ( this.status != status );
        // guarded by barrier - unused if status is planning, stable otherwise
        long compilationCompletedNanos = this.compilationCompletedNanos;
        // guarded by barrier - like compilationCompletedNanos
        CompilerInfo planner = status.isPlanning() ? null : this.compilerInfo;
        List<ActiveLock> waitingOnLocks = status.isWaitingOnLocks() ? status.waitingOnLocks() : Collections.emptyList();
        // activeLockCount is not atomic to capture, so we capture it after the most sensitive part.
        long totalActiveLocks = this.activeLockCount.getAsLong();
        // just needs to be captured at some point...
        long heapAllocatedBytes = heapAllocation.allocatedBytes( threadExecutingTheQueryId );
        PageCounterValues pageCounters = new PageCounterValues( pageCursorCounters );

        // - at this point we are done capturing the "live" state, and can start computing the snapshot -
        long compilationTimeNanos = (status.isPlanning() ? currentTimeNanos : compilationCompletedNanos) - startTimeNanos;
        long elapsedTimeNanos = currentTimeNanos - startTimeNanos;
        cpuTimeNanos -= cpuTimeNanosWhenQueryStarted;
        waitTimeNanos += status.waitTimeNanos( currentTimeNanos );
        // TODO: when we start allocating native memory as well during query execution,
        // we should have a tracer that keeps track of how much memory we have allocated for the query,
        // and get the value from that here.
        heapAllocatedBytes = heapAllocatedBytesWhenQueryStarted < 0 ? -1 : // mark that we were unable to measure
                heapAllocatedBytes - heapAllocatedBytesWhenQueryStarted;

        return new QuerySnapshot(
                this,
                planner,
                pageCounters,
                NANOSECONDS.toMicros( compilationTimeNanos ),
                NANOSECONDS.toMicros( elapsedTimeNanos ),
                cpuTimeNanos == 0 && cpuTimeNanosWhenQueryStarted == -1 ? -1 : NANOSECONDS.toMicros( cpuTimeNanos ),
                NANOSECONDS.toMicros( waitTimeNanos ),
                status.name(),
                status.toMap( currentTimeNanos ),
                waitingOnLocks,
                totalActiveLocks - initialActiveLocks,
                heapAllocatedBytes
        );
    }

    public  ExecutingQuery(
            long queryId,
            ClientConnectionInfo clientConnection,
            String username,
            String queryText,
            MapValue queryParameters,
            Map<String,Object> transactionAnnotationData,
            LongSupplier activeLockCount,
            PageCursorCounters pageCursorCounters,
            long threadExecutingTheQueryId,
            String threadExecutingTheQueryName,
            SystemNanoClock clock,
            CpuClock cpuClock,
            HeapAllocation heapAllocation );

}