44import re
55import datetime
66import matplotlib .pyplot as plt
7+ from collections import OrderedDict
78
89
910def parse_updatetip_line (line ):
@@ -35,6 +36,7 @@ def parse_leveldb_generated_table_line(line):
3536 parsed_datetime = datetime .datetime .strptime (iso_str , "%Y-%m-%dT%H:%M:%SZ" )
3637 return parsed_datetime , int (keys_count_str ), int (bytes_count_str )
3738
39+
3840def parse_validation_txadd_line (line ):
3941 match = re .match (r'^([\d\-:TZ]+) \[validation] TransactionAddedToMempool: txid=.+wtxid=.+' , line )
4042 if not match :
@@ -61,6 +63,7 @@ def parse_coindb_commit_line(line):
6163 parsed_datetime = datetime .datetime .strptime (iso_str , "%Y-%m-%dT%H:%M:%SZ" )
6264 return parsed_datetime , int (txout_count_str )
6365
66+
6467def parse_log_file (log_file ):
6568 with open (log_file , 'r' , encoding = 'utf-8' ) as f :
6669 update_tip_data = []
@@ -94,7 +97,7 @@ def parse_log_file(log_file):
9497 return update_tip_data , leveldb_compact_data , leveldb_gen_table_data , validation_txadd_data , coindb_write_batch_data , coindb_commit_data
9598
9699
97- def generate_plot (x , y , x_label , y_label , title , output_file ):
100+ def generate_plot (x , y , x_label , y_label , title , output_file , is_height_based = False ):
98101 if not x or not y :
99102 print (f"Skipping plot '{ title } ' as there is no data." )
100103 return
@@ -105,6 +108,82 @@ def generate_plot(x, y, x_label, y_label, title, output_file):
105108 plt .xlabel (x_label , fontsize = 16 )
106109 plt .ylabel (y_label , fontsize = 16 )
107110 plt .grid (True )
111+
112+ # Make sure the x-axis covers the full data range
113+ min_x , max_x = min (x ), max (x )
114+ plt .xlim (min_x , max_x )
115+
116+ # Add vertical lines for major protocol upgrades if this is a height-based plot
117+ if is_height_based :
118+ # Define all notable heights from the chainparams file
119+ fork_heights = OrderedDict ([
120+ ('BIP34' , 227931 ), # Block v2, coinbase includes height
121+ ('BIP66' , 363725 ), # Strict DER signatures
122+ ('BIP65' , 388381 ), # OP_CHECKLOCKTIMEVERIFY
123+ ('CSV' , 419328 ), # BIP68, 112, 113 - OP_CHECKSEQUENCEVERIFY
124+ ('Segwit' , 481824 ), # BIP141, 143, 144, 145 - Segregated Witness
125+ ('Taproot' , 709632 ), # BIP341, 342 - Schnorr signatures & Taproot
126+ ('Halving 1' , 210000 ), # First halving
127+ ('Halving 2' , 420000 ), # Second halving
128+ ('Halving 3' , 630000 ), # Third halving
129+ ('Halving 4' , 840000 ), # Fourth halving
130+ ])
131+
132+ # Colors for the different types of events
133+ fork_colors = {
134+ 'BIP34' : 'blue' ,
135+ 'BIP66' : 'blue' ,
136+ 'BIP65' : 'blue' ,
137+ 'CSV' : 'blue' ,
138+ 'Segwit' : 'green' ,
139+ 'Taproot' : 'red' ,
140+ 'Halving 1' : 'purple' ,
141+ 'Halving 2' : 'purple' ,
142+ 'Halving 3' : 'purple' ,
143+ 'Halving 4' : 'purple' ,
144+ }
145+
146+ # Line styles for different types of events
147+ fork_styles = {
148+ 'BIP34' : '--' ,
149+ 'BIP66' : '--' ,
150+ 'BIP65' : '--' ,
151+ 'CSV' : '--' ,
152+ 'Segwit' : '--' ,
153+ 'Taproot' : '--' ,
154+ 'Halving 1' : ':' ,
155+ 'Halving 2' : ':' ,
156+ 'Halving 3' : ':' ,
157+ 'Halving 4' : ':' ,
158+ }
159+
160+ max_y = max (y )
161+
162+ # Position text labels at different heights to avoid overlap
163+ text_positions = {}
164+ position_increment = max_y * 0.05
165+ current_position = max_y * 0.9
166+
167+ # Add lines for forks that are in range
168+ for fork_name , height in fork_heights .items ():
169+ if min_x <= height <= max_x :
170+ plt .axvline (x = height , color = fork_colors [fork_name ],
171+ linestyle = fork_styles [fork_name ])
172+
173+ # Avoid label overlaps by staggering vertical positions
174+ if height in text_positions :
175+ # If this x position already has a label, adjust position
176+ text_positions [height ] -= position_increment
177+ else :
178+ text_positions [height ] = current_position
179+ current_position -= position_increment
180+ if current_position < max_y * 0.1 :
181+ current_position = max_y * 0.9 # Reset if we're too low
182+
183+ plt .text (height , text_positions [height ], f'{ fork_name } ({ height } )' ,
184+ rotation = 90 , verticalalignment = 'top' ,
185+ color = fork_colors [fork_name ])
186+
108187 plt .xticks (rotation = 90 , fontsize = 12 )
109188 plt .yticks (fontsize = 12 )
110189 plt .tight_layout ()
@@ -133,10 +212,10 @@ def generate_plot(x, y, x_label, y_label, title, output_file):
133212 float_minutes = [(t - times [0 ]).total_seconds () / 60 for t in times ]
134213
135214 generate_plot (float_minutes , heights , "Elapsed minutes" , "Block Height" , "Block Height vs Time" , os .path .join (png_dir , f"{ commit } -height_vs_time.png" ))
136- generate_plot (heights , cache_size , "Block Height" , "Cache Size (MiB)" , "Cache Size vs Block Height" , os .path .join (png_dir , f"{ commit } -cache_vs_height.png" ))
215+ generate_plot (heights , cache_size , "Block Height" , "Cache Size (MiB)" , "Cache Size vs Block Height" , os .path .join (png_dir , f"{ commit } -cache_vs_height.png" ), is_height_based = True )
137216 generate_plot (float_minutes , cache_size , "Elapsed minutes" , "Cache Size (MiB)" , "Cache Size vs Time" , os .path .join (png_dir , f"{ commit } -cache_vs_time.png" ))
138- generate_plot (heights , tx_counts , "Block Height" , "Transaction Count" , "Transactions vs Block Height" , os .path .join (png_dir , f"{ commit } -tx_vs_height.png" ))
139- generate_plot (heights , cache_count , "Block Height" , "Coins Cache Size" , "Coins Cache Size vs Height" , os .path .join (png_dir , f"{ commit } -coins_cache_vs_height.png" ))
217+ generate_plot (heights , tx_counts , "Block Height" , "Transaction Count" , "Transactions vs Block Height" , os .path .join (png_dir , f"{ commit } -tx_vs_height.png" ), is_height_based = True )
218+ generate_plot (heights , cache_count , "Block Height" , "Coins Cache Size" , "Coins Cache Size vs Height" , os .path .join (png_dir , f"{ commit } -coins_cache_vs_height.png" ), is_height_based = True )
140219
141220 # LevelDB Compaction and Generated Tables
142221 if leveldb_compact_data :
@@ -165,4 +244,4 @@ def generate_plot(x, y, x_label, y_label, title, output_file):
165244 coindb_commit_float_minutes = [(t - times [0 ]).total_seconds () / 60 for t in coindb_commit_times ]
166245 generate_plot (coindb_commit_float_minutes , txout_counts , "Elapsed minutes" , "Transaction Output Count" , "Coin Database Transaction Output Committed vs Time" , os .path .join (png_dir , f"{ commit } -coindb_commit_txout_vs_time.png" ))
167246
168- print ("Plots saved!" )
247+ print ("Plots saved!" )
0 commit comments