If you are using SQL Server 2016, the Direct Query Statistics feature allows you to view the insertion progress in real time.
A screenshot was taken when inserting 10 million rows into a table with a clustered index and one nonclustered index.
This shows that the insert was 88% full at the clustered index, and it was followed by a sort operator to get the values ββin the order of the non-clustered index before inserting into the NCI. This is a lock statement, and sorting cannot output lines until all input lines have been consumed, so the statements to the left of them will be executed at 0%.

Regarding your question on NOLOCK
Trivial check
Compound 1
USE tempdb CREATE TABLE T2 ( X INT IDENTITY PRIMARY KEY, F CHAR(8000) ); WHILE NOT EXISTS(SELECT * FROM T2 WITH (NOLOCK)) LOOP: SELECT COUNT(*) AS CountMethod FROM T2 WITH (NOLOCK); SELECT rows FROM sysindexes WHERE id = OBJECT_ID('T2'); RAISERROR ('Waiting for 10 seconds',0,1) WITH NOWAIT; WAITFOR delay '00:00:10'; SELECT COUNT(*) AS CountMethod FROM T2 WITH (NOLOCK); SELECT rows FROM sysindexes WHERE id = OBJECT_ID('T2'); RAISERROR ('Waiting to drop table',0,1) WITH NOWAIT DROP TABLE T2
Compound 2
use tempdb; --Insert 2000 * 2000 = 4 million rows WITH T AS (SELECT TOP 2000 'x' AS x FROM master..spt_values) INSERT INTO T2 (F) SELECT 'X' FROM T v1 CROSS JOIN T v2 OPTION (MAXDOP 1)
Sample Results - Showing Row Increase

SELECT queries with NOLOCK allow dirty reads. They actually don't lock the locks and can still be locked, they still need to lock the SCH-S (circuit stability) on the table ( and in the heap it will also accept the hobt lock ).
The only thing incompatible with SCH-S is the SCH-M lock (circuit modification). Presumably, you also executed some DDL in the table in the same transaction (for example, you might have created it in the same channel)
In the case of using a large insert, where the approximate result of the flight is good, I usually just polled sysindexes , as shown above, to extract the count from the metadata, and not to actually count the rows ( non-obsolete alternative DMVs are available )
When an insert has a broad update plan , you can even see how it inserts various indexes there.
If a table is created inside an insert transaction, this sysindexes request will still be blocked, although the OBJECT_ID function OBJECT_ID not return a result based on uncommitted data regardless of isolation level. Sometimes you can get around this by sys.tables NOLOCK instead of object_id from sys.tables .