1
1
#!/usr/bin/env python3
2
2
# Copyright (C) 2022 CESNET z. s. p. o.
3
3
# Author(s): Lukas Nevrkla <[email protected] >
4
+ #
5
+ # Package for loading statistics from mem_logger component
4
6
5
- import json
6
7
import argparse
8
+ import numpy as np
7
9
import nfb
8
10
from data_logger .data_logger import DataLogger
11
+ import logger_stats .logger_stats as Stats
9
12
10
13
11
14
class MemLogger (DataLogger ):
12
15
13
16
DT_COMPATIBLE = "netcope,mem_logger"
14
17
15
- _BIT_LATENCY_TO_FIRST = 0
16
-
17
18
def __init__ (self , ** kwargs ):
18
19
try :
19
20
super ().__init__ (** kwargs )
21
+ except Exception as e :
22
+ raise Exception (f"ERROR while opening MemLogger component:\n { e } " )
20
23
21
- ctrli = self .load_ctrl (False )
22
- self .config ["MEM_DATA_WIDTH" ] = self .get_bits (ctrli , self .mi_width , self .mi_width * 0 )
23
- self .config ["MEM_ADDR_WIDTH" ] = self .get_bits (ctrli , self .mi_width , self .mi_width * 1 )
24
- self .config ["MEM_BURST_WIDTH" ] = self .get_bits (ctrli , self .mi_width , self .mi_width * 2 )
25
- self .config ["MEM_FREQ_KHZ" ] = self .get_bits (ctrli , self .mi_width , self .mi_width * 3 )
26
-
27
- except Exception :
28
- print ("ERROR while opening MemLogger component!\n Maybe unsupported FPGA firmware?!" )
29
- exit (1 )
24
+ self .stats = self .init_stats ()
30
25
31
26
def set_config (self , latency_to_first ):
32
27
self .set_ctrlo (latency_to_first & 1 )
33
28
34
- def ticks_to_s (self , ticks ):
35
- return ticks / (self .config ["MEM_FREQ_KHZ" ] * 1000.0 )
36
-
37
- def ticks_to_flow (self , words , ticks ):
38
- s = self .ticks_to_s (ticks )
39
- if s == 0 :
40
- return 0
41
- return (words * self .config ["MEM_DATA_WIDTH" ]) / s
42
-
43
- # Remove leading and trailing zeros
44
- def trim_hist (self , data ):
45
- res = {}
46
- res_tmp = {}
47
-
48
- started = False
49
- for k , v in data .items ():
50
- if v != 0 or started :
51
- res_tmp [k ] = v
52
- started = True
53
- elif v == 0 or not started :
54
- res_tmp = {k : v }
55
- if v != 0 :
56
- res = res_tmp .copy ()
57
-
58
- return res
59
-
60
- def latency_hist_step (self ):
61
- hist_max = 2 ** self .config ["VALUE_WIDTH" ][0 ]
62
- hist_step = hist_max / self .config ["HIST_BOX_CNT" ][0 ]
63
- return self .ticks_to_s (hist_step - 1 ) * 10 ** 9
64
-
65
- def load_stats (self ):
66
- stats = {}
67
-
68
- ctrlo = self .load_ctrl (True )
69
- stats ["latency_to_first" ] = (ctrlo >> self ._BIT_LATENCY_TO_FIRST ) & 1
70
-
71
- # Cnters
72
- stats ["wait" ] = self .load_cnter (0 )
73
- stats ["request_hold" ] = self .load_cnter (1 )
74
- stats ["rdy_hold_write" ] = self .load_cnter (2 )
75
- stats ["rdy_hold_read" ] = self .load_cnter (3 )
76
-
77
- stats ["wr_ticks" ] = self .load_cnter (4 )
78
- stats ["rd_ticks" ] = self .load_cnter (5 )
79
- stats ["total_ticks" ] = self .load_cnter (6 )
80
- stats ["wr_req_cnt" ] = self .load_cnter (7 )
81
- stats ["wr_req_words" ] = self .load_cnter (8 )
82
- stats ["rd_req_cnt" ] = self .load_cnter (9 )
83
- stats ["rd_req_words" ] = self .load_cnter (10 )
84
- stats ["rd_resp_words" ] = self .load_cnter (11 )
85
- stats ["err_zero_burst" ] = self .load_cnter (12 )
86
- stats ["err_simult_rw" ] = self .load_cnter (13 )
87
-
88
- # Values
89
- stats ["latency" ] = self .load_value (0 )
29
+ def init_stats (self ):
30
+ stats = Stats .LoggerStats ('Mem logger' , logger = self )
31
+
32
+ # Constants #
33
+
34
+ constants = [
35
+ "MEM_DATA_WIDTH" ,
36
+ "MEM_ADDR_WIDTH" ,
37
+ "MEM_BURST_WIDTH" ,
38
+ "MEM_FREQ_KHZ" ,
39
+ ]
40
+ stats .add_stats (
41
+ name = 'Constants' ,
42
+ names = constants ,
43
+ indexes = list (range (len (constants ))),
44
+ constructor = lambda i , n : Stats .Constant (i , n )
45
+ )
46
+ stats .load ()
47
+
48
+ freq = stats ['Constants' ]['MEM_FREQ_KHZ' ] * 1000.0
49
+ word_b = stats ['Constants' ]['MEM_DATA_WIDTH' ]
50
+
51
+ # Counters #
52
+
53
+ counters = [
54
+ "no req + not rdy" ,
55
+ "no req + rdy" ,
56
+ "wr + not rdy" ,
57
+ "rd + not rdy" ,
58
+ "wr ticks" ,
59
+ "rd ticks" ,
60
+ "total ticks" ,
61
+ "wr req cnt" ,
62
+ "wr req words" ,
63
+ "rd req cnt" ,
64
+ "rd req words" ,
65
+ "rd resp words" ,
66
+ "err zero burst" ,
67
+ "err simult rw" ,
68
+ ]
69
+
70
+ def counter_i (name ):
71
+ return counters .index (name )
72
+
73
+ # Requests #
74
+
75
+ reqs = ['wr req cnt' , 'wr req words' , 'rd req cnt' , 'rd req words' , 'rd resp words' ]
76
+ stats .add_stats (
77
+ name = 'Requests' ,
78
+ names = reqs ,
79
+ indexes = list (map (counter_i , reqs )),
80
+ constructor = lambda i , n : Stats .Counter (i , n )
81
+ )
82
+
83
+ # Data flow #
84
+
85
+ stats_flow = Stats .LoggerStats ('Data flow' )
86
+ stats .add_stat (stats_flow )
87
+
88
+ stats_flow .add_stat (Stats .FlowCounter (
89
+ index_words = counter_i ('wr req words' ), index_ticks = counter_i ('wr ticks' ),
90
+ freq = freq , word_bits = word_b , name = 'wr flow'
91
+ ))
92
+ stats_flow .add_stat (Stats .FlowCounter (
93
+ index_words = counter_i ('rd resp words' ), index_ticks = counter_i ('rd ticks' ),
94
+ freq = freq , word_bits = word_b , name = 'rd flow'
95
+ ))
96
+
97
+ # Values #
98
+
99
+ stats_val = Stats .LoggerStats ('Values' )
100
+ stats .add_stat (stats_val )
101
+
102
+ stats_val .add_stat (Stats .Value (
103
+ index = 0 , name = 'latency' ,
104
+ convert = Stats .ConvertTime (freq , units = 'ns' ),
105
+ format = Stats .FormatDefaultValue (units = 'ns' , format = Stats .FormatDefault (decimal = 1 ))
106
+ ))
90
107
if self .config ["VALUE_CNT" ] > 1 :
91
- stats ["paralel_read" ] = self .load_value (1 )
92
-
93
- # Calculate time and flow
94
- stats ["wr_time_ms" ] = self .ticks_to_s (stats ['wr_ticks' ]) * 10 ** 3
95
- stats ["wr_flow_gbs" ] = self .ticks_to_flow (stats ['wr_req_words' ], stats ['wr_ticks' ]) / 10 ** 9
96
-
97
- stats ["rd_time_ms" ] = self .ticks_to_s (stats ['rd_ticks' ]) * 10 ** 3
98
- stats ["rd_flow_gbs" ] = self .ticks_to_flow (stats ['rd_resp_words' ], stats ['rd_ticks' ]) / 10 ** 9
99
-
100
- stats ["total_time_ms" ] = self .ticks_to_s (stats ['total_ticks' ]) * 10 ** 3
101
- stats ["total_flow_gbs" ] = self .ticks_to_flow (stats ['wr_req_words' ] + stats ['rd_resp_words' ], stats ['total_ticks' ]) / 10 ** 9
102
-
103
- # Calculate latency
104
- stats ["latency" ]["min_ns" ] = self .ticks_to_s (stats ["latency" ]["min" ]) * 10 ** 9
105
- stats ["latency" ]["max_ns" ] = self .ticks_to_s (stats ["latency" ]["max" ]) * 10 ** 9
106
- stats ["latency" ]["avg_ns" ] = self .ticks_to_s (stats ["latency" ]["avg" ]) * 10 ** 9
107
-
108
- # Calculate latency histogram
109
- hist_step = self .config ["HIST_STEP" ][0 ]
110
- stats ["latency" ]["hist_ns" ] = {}
108
+ stats_val .add_stat (Stats .Value (index = 1 , name = 'paralel reads' ))
109
+
110
+ # Duration #
111
+
112
+ stats_dur = Stats .LoggerStats ('Test duration' )
113
+ stats .add_stat (stats_dur )
114
+
115
+ stats_dur .add_stat (Stats .TimeCounter (
116
+ index = counter_i ('wr ticks' ), freq = freq ,
117
+ name = 'wr time' , units = 'ms'
118
+ ))
119
+ stats_dur .add_stat (Stats .TimeCounter (
120
+ index = counter_i ('rd ticks' ), freq = freq ,
121
+ name = 'rd time' , units = 'ms'
122
+ ))
123
+ stats_dur .add_stat (Stats .TimeCounter (
124
+ index = counter_i ('total ticks' ), freq = freq ,
125
+ name = 'total time' , units = 'ms'
126
+ ))
127
+
128
+ # Errors #
129
+
130
+ errs = ['err zero burst' , 'err simult rw' ]
131
+ stats .add_stats (
132
+ name = 'Errors' ,
133
+ names = errs ,
134
+ indexes = list (map (counter_i , errs )),
135
+ constructor = lambda i , n : Stats .Counter (i , n )
136
+ )
137
+
138
+ # Ready signal status #
139
+
140
+ rdy_status = ["no req + not rdy" , "no req + rdy" , "wr + not rdy" , "rd + not rdy" ]
141
+ stats .add_stats (
142
+ name = 'Ready signal status' ,
143
+ names = rdy_status ,
144
+ indexes = list (map (counter_i , rdy_status )),
145
+ constructor = lambda i , n : Stats .Counter (i , n )
146
+ )
147
+
148
+ # Special statistics #
149
+
150
+ BIT_LATENCY_TO_FIRST = 0
151
+ ctrlo = self .load_ctrl (True )
152
+ latency_to_first = (ctrlo >> BIT_LATENCY_TO_FIRST ) & 1
153
+ stats .add_stat (Stats .Custom (name = 'latency to first word' , data = latency_to_first ))
111
154
112
- for i , v in enumerate (stats ["latency" ]["hist" ]):
113
- end = self .ticks_to_s ((i + 1 ) * hist_step - 1 ) * 10 ** 9
114
- stats ["latency" ]["hist_ns" ][end ] = v
115
- stats ["latency" ]["hist_ns" ] = self .trim_hist (stats ["latency" ]["hist_ns" ])
155
+ stats .add_calc_stats (self .calc_stats )
116
156
117
157
return stats
118
158
119
- def stats_to_json (self , stats ):
120
- res = json .dumps (stats , indent = 4 )
121
- print (res )
122
-
123
- def line_to_str (self , txt , val , unit = "" ):
124
- if isinstance (val , int ):
125
- val = f"{ val :<15} "
126
- elif isinstance (val , float ):
127
- val = f"{ val :< .2f} "
128
-
129
- if unit != "" :
130
- unit = f"[{ unit } ]"
131
- return f"{ txt :<20} { val } { unit } \n "
132
-
133
- def stats_to_str (self , stats ):
134
- res = ""
135
- res += "Mem_logger statistics:\n "
136
- res += "----------------------\n "
137
- res += self .line_to_str ("write requests " , stats ['wr_req_cnt' ])
138
- res += self .line_to_str (" write words " , stats ['wr_req_words' ])
139
- res += self .line_to_str ("read requests " , stats ['rd_req_cnt' ])
140
- res += self .line_to_str (" requested words" , stats ['rd_req_words' ])
141
- res += self .line_to_str (" received words " , stats ['rd_resp_words' ])
142
- res += "Handshakes:\n "
143
- res += self .line_to_str (" avmm rdy hold " , stats ['rdy_hold_read' ] + stats ['rdy_hold_write' ])
144
- res += self .line_to_str (" avmm rdy hold (rd) " , stats ['rdy_hold_read' ])
145
- res += self .line_to_str (" avmm rdy hold (wr) " , stats ['rdy_hold_write' ])
146
- res += self .line_to_str (" no request " , stats ["request_hold" ])
147
- res += self .line_to_str (" wait " , stats ["wait" ])
148
- res += "Flow:\n "
149
- res += self .line_to_str (" write" , stats ['wr_flow_gbs' ], "Gb/s" )
150
- res += self .line_to_str (" read " , stats ['rd_flow_gbs' ], "Gb/s" )
151
- res += self .line_to_str (" total" , stats ['total_flow_gbs' ], "Gb/s" )
152
- res += "Time:\n "
153
- res += self .line_to_str (" write" , stats ['wr_time_ms' ], "ms" )
154
- res += self .line_to_str (" read " , stats ['rd_time_ms' ], "ms" )
155
- res += self .line_to_str (" total" , stats ['total_time_ms' ], "ms" )
156
- res += "Latency:\n "
157
- res += self .line_to_str (" min" , stats ['latency' ]["min_ns" ], "ns" )
158
- res += self .line_to_str (" max" , stats ['latency' ]["max_ns" ], "ns" )
159
- res += self .line_to_str (" avg" , stats ['latency' ]["avg_ns" ], "ns" )
160
- res += " histogram [ns]:\n "
161
- if len (stats ['latency' ]['hist_ns' ]) > 0 :
162
- prev = 0
163
- for k , v in stats ['latency' ]['hist_ns' ].items ():
164
- if v != 0 :
165
- res += self .line_to_str (f" { prev :> 6.1f} -{ k :> 6.1f} ..." , v )
166
- prev = k
167
- res += "Errors:\n "
168
- res += self .line_to_str (" zero burst count" , stats ['err_zero_burst' ])
169
- res += self .line_to_str (" simultaneous r+w" , stats ['err_simult_rw' ])
170
-
171
- if self .config ['VALUE_CNT' ] > 1 :
172
- res += "Paralel reads count:\n "
173
- res += self .line_to_str (" min" , stats ['paralel_read' ]["min" ], "" )
174
- res += self .line_to_str (" max" , stats ['paralel_read' ]["max" ], "" )
175
- res += self .line_to_str (" avg" , stats ['paralel_read' ]["avg" ], "" )
176
-
177
- hist_step = self .config ["HIST_STEP" ][1 ]
178
- prev = 0
179
- for i , v in enumerate (stats ["paralel_read" ]["hist" ]):
180
- if v != 0 :
181
- res += self .line_to_str (f" { prev :> 6.1f} -{ hist_step * (i + 1 ):> 6.1f} ..." , v )
182
- prev = hist_step * (i + 1 )
183
-
184
- return res
185
-
186
- def config_to_str (self ):
187
- res = ""
188
- res += "Mem_logger config:\n "
189
- res += "------------------\n "
190
- res += f"MEM_DATA_WIDTH: { self .config ['MEM_DATA_WIDTH' ]} \n "
191
- res += f"MEM_ADDR_WIDTH: { self .config ['MEM_ADDR_WIDTH' ]} \n "
192
- res += f"MEM_BURST_WIDTH { self .config ['MEM_BURST_WIDTH' ]} \n "
193
- res += f"MEM_FREQ_KHZ: { self .config ['MEM_FREQ_KHZ' ]} \n "
194
- res += f"LATENCY_WIDTH: { self .config ['VALUE_WIDTH' ][0 ]} \n "
195
- res += f"HIST_BOX_CNT: { self .config ['HIST_BOX_CNT' ][0 ]} \n "
196
- res += f"HIST_BOX_WIDTH: { self .config ['HIST_BOX_WIDTH' ][0 ]} \n "
197
- res += "\n "
198
- return res
199
-
200
- def print (self ):
201
- print (self .config_to_str ())
202
-
203
- stats = self .load_stats ()
204
- print (self .stats_to_str (stats ))
159
+ def calc_stats (self , data ):
160
+ data ['Data flow' ]['total flow' ] = np .array (data ['Data flow' ]['wr flow' ]) + np .array (data ['Data flow' ]['rd flow' ])
161
+
162
+ return data
205
163
206
164
207
165
def parseParams ():
@@ -210,25 +168,39 @@ def parseParams():
210
168
)
211
169
212
170
access = parser .add_argument_group ('card access arguments' )
213
- access .add_argument ('-d' , '--device' , default = nfb .libnfb .Nfb .default_device ,
214
- metavar = 'device' , help = """device with target FPGA card""" )
215
- access .add_argument ('-i' , '--index' , type = int , metavar = 'index' , default = 0 , help = """index inside DevTree""" )
171
+ access .add_argument (
172
+ '-d' , '--device' , default = nfb .libnfb .Nfb .default_dev_path ,
173
+ metavar = 'device' , help = """device with target FPGA card"""
174
+ )
175
+ access .add_argument (
176
+ '-i' , '--index' , type = int , metavar = 'index' ,
177
+ default = 0 , help = """index inside DevTree"""
178
+ )
216
179
217
180
common = parser .add_argument_group ('common arguments' )
218
- #common.add_argument('-p', '--print', action='store_true', help = """print registers""")
219
- common .add_argument ('--rst' , action = 'store_true' , help = """reset mem_tester and mem_logger""" )
181
+ common .add_argument (
182
+ '--rst' , action = 'store_true' ,
183
+ help = """reset mem_tester and mem_logger"""
184
+ )
185
+ common .add_argument (
186
+ '-j' , '--stats-json' , action = 'store_true' ,
187
+ help = """prints mem_logger statistics in json"""
188
+ )
220
189
args = parser .parse_args ()
221
190
return args
222
191
223
192
224
193
if __name__ == '__main__' :
225
194
args = parseParams ()
226
195
logger = MemLogger (dev = args .device , index = args .index )
196
+ logger .stats .load ()
227
197
228
- #if args.print:
229
- logger .print ()
198
+ print (logger .stats .to_str ())
230
199
231
200
if args .rst :
232
201
logger .rst ()
233
202
203
+ if args .stats_json :
204
+ print (logger .stats .data ())
205
+
234
206
#logger.set_config(latency_to_first=True)
0 commit comments