trident_fuzz_metrics/
lib.rs

1#![allow(dead_code)]
2
3mod regression;
4mod transactions;
5pub mod types;
6use std::fs::File;
7use std::io::Write;
8
9use solana_sdk::account::AccountSharedData;
10use types::Seed;
11
12use crate::regression::regression::FuzzingRegression;
13use crate::transactions::transaction_stats::FuzzingStatistics;
14
15pub use crate::regression::compare::compare_regression_files;
16pub use crate::regression::compare::ComparisonResult;
17
18#[derive(Clone, Default)]
19pub struct TridentFuzzingData {
20    master_seed: Option<String>,
21    metrics: FuzzingStatistics,
22    regression: FuzzingRegression,
23}
24
25// Metrics
26impl TridentFuzzingData {
27    pub fn add_to_histogram(&mut self, metric_name: &str, value: f64) {
28        self.metrics.add_to_histogram(metric_name, value);
29    }
30
31    pub fn add_to_accumulator(&mut self, metric_name: &str, value: f64) {
32        self.metrics.add_to_accumulator(metric_name, value);
33    }
34
35    pub fn add_executed_transaction(&mut self, transaction_name: &str) {
36        self.metrics.add_executed_transaction(transaction_name);
37    }
38
39    pub fn add_successful_transaction(&mut self, transaction_name: &str) {
40        self.metrics.add_successful_transaction(transaction_name);
41    }
42
43    pub fn add_failed_transaction(
44        &mut self,
45        transaction_name: &str,
46        error: String,
47        logs: Option<Vec<String>>,
48    ) {
49        self.metrics
50            .add_failed_transaction(transaction_name, error, logs);
51    }
52
53    pub fn add_transaction_panicked(
54        &mut self,
55        transaction_name: &str,
56        seed: Seed,
57        panic: String,
58        logs: Option<Vec<String>>,
59        instruction_inputs: String,
60    ) {
61        self.metrics.add_transaction_panicked(
62            transaction_name,
63            seed,
64            panic,
65            logs,
66            instruction_inputs,
67        );
68    }
69
70    pub fn add_custom_instruction_error(
71        &mut self,
72        transaction_name: &str,
73        error_code: &u32,
74        logs: Option<Vec<String>>,
75    ) {
76        self.metrics
77            .add_custom_instruction_error(transaction_name, error_code, logs);
78    }
79}
80
81// Regression
82impl TridentFuzzingData {
83    pub fn add_to_regression(
84        &mut self,
85        iteration_seed: &str,
86        account_name: &str,
87        account_shared_data: &AccountSharedData,
88    ) {
89        self.regression
90            .add_to_regression(iteration_seed, account_name, account_shared_data);
91    }
92}
93
94// Master seed
95impl TridentFuzzingData {
96    pub fn with_master_seed(seed: Seed) -> Self {
97        Self {
98            master_seed: Some(hex::encode(seed)),
99            metrics: FuzzingStatistics::new(),
100            regression: FuzzingRegression::default(),
101        }
102    }
103    pub fn add_master_seed(&mut self, seed: &str) {
104        self.master_seed = Some(seed.to_string());
105    }
106}
107
108// Generation
109impl TridentFuzzingData {
110    pub fn generate(&self) -> std::io::Result<()> {
111        // Generate metrics JSON if FUZZING_METRICS is set
112        if std::env::var("FUZZING_METRICS").is_ok() {
113            self.metrics.show_table();
114
115            if let Ok(metrics_file_name) = std::env::var("FUZZING_JSON") {
116                self.to_json(&metrics_file_name);
117            }
118
119            // Generate HTML dashboard if FUZZING_DASHBOARD is set
120            if let Ok(dashboard_file_name) = std::env::var("FUZZING_DASHBOARD") {
121                self.to_html(&dashboard_file_name)?;
122            }
123        }
124
125        // Generate regression JSON if FUZZING_REGRESSION is set
126        if let Ok(regression_file_name) = std::env::var("FUZZING_REGRESSION") {
127            self.to_regression_json(&regression_file_name)?;
128        }
129
130        Ok(())
131    }
132
133    fn to_json(&self, path: &str) {
134        let mut file = File::create(path).unwrap();
135
136        // Create a copy and finalize custom metrics for proper serialization
137        let mut metrics_copy = self.metrics.clone();
138        for metric in metrics_copy.custom_metrics.values_mut() {
139            metric.finalize_histogram();
140        }
141
142        // Create the data structure for JSON serialization with clean structure
143        let mut json_data = serde_json::Map::new();
144
145        // Include master seed at the start
146        json_data.insert("master_seed".to_string(), self.master_seed.clone().into());
147
148        // Include all metrics data
149        json_data.insert(
150            "transactions".to_string(),
151            serde_json::to_value(&metrics_copy.transactions).unwrap_or_default(),
152        );
153        json_data.insert(
154            "custom_metrics".to_string(),
155            serde_json::to_value(&metrics_copy.custom_metrics).unwrap_or_default(),
156        );
157
158        // Include state hash at the end if regression is enabled and state exists
159        if let Ok(state_hash) = self.regression.get_snapshots_hash() {
160            json_data.insert("state_snapshots_hash".to_string(), state_hash.into());
161        }
162
163        let serialized = serde_json::to_string_pretty(&json_data).unwrap();
164        file.write_all(serialized.as_bytes()).unwrap();
165    }
166
167    fn to_html(&self, path: &str) -> std::io::Result<()> {
168        let html_content = self.create_dashboard_html();
169        let mut file = File::create(path)?;
170        file.write_all(html_content.as_bytes())?;
171        Ok(())
172    }
173
174    /// Create the HTML content for the dashboard
175    fn create_dashboard_html(&self) -> String {
176        let template = include_str!("dashboard-template/dashboard_template.html");
177
178        // Transform the internal data structure to match the expected dashboard format
179        let dashboard_data = self.create_dashboard_data_structure();
180        let json_data = serde_json::to_string_pretty(&dashboard_data).unwrap();
181
182        template.replace("{{JSON_DATA}}", &json_data)
183    }
184    /// Transform internal transaction stats to dashboard-friendly format
185    fn create_dashboard_data_structure(&self) -> serde_json::Value {
186        // Create a mutable copy to finalize histograms for display
187        let mut custom_metrics_for_display = self.metrics.custom_metrics.clone();
188        for metric in custom_metrics_for_display.values_mut() {
189            metric.finalize_histogram();
190        }
191        let mut instructions = serde_json::Map::new();
192
193        for (transaction_name, stats) in &self.metrics.transactions {
194            let mut instruction_data = serde_json::Map::new();
195            instruction_data.insert("invoked".to_string(), stats.transaction_invoked.into());
196            instruction_data.insert(
197                "transactions_successful".to_string(),
198                stats.transaction_successful.into(),
199            );
200            instruction_data.insert(
201                "transactions_failed".to_string(),
202                stats.transaction_failed.into(),
203            );
204            instruction_data.insert(
205                "transactions_panicked".to_string(),
206                stats.transaction_panicked.into(),
207            );
208
209            // Convert internal error structures to dashboard format
210            let transactions_errors = stats.transactions_errors.to_dashboard_format();
211            let custom_instruction_errors = stats.custom_instruction_errors.to_dashboard_format();
212            let transactions_panics = stats.transactions_panics.to_dashboard_format();
213
214            instruction_data.insert("transactions_errors".to_string(), transactions_errors);
215            instruction_data.insert(
216                "custom_instruction_errors".to_string(),
217                custom_instruction_errors,
218            );
219            instruction_data.insert("transactions_panics".to_string(), transactions_panics);
220
221            instructions.insert(transaction_name.clone(), instruction_data.into());
222        }
223
224        let mut result = serde_json::Map::new();
225        result.insert("instructions".to_string(), instructions.into());
226        result.insert(
227            "custom_metrics".to_string(),
228            serde_json::to_value(&custom_metrics_for_display).unwrap_or_default(),
229        );
230
231        // Always include master seed (null if not set)
232        result.insert("master_seed".to_string(), self.master_seed.clone().into());
233
234        // Include state snapshots hash if available
235        if let Ok(state_hash) = self.regression.get_snapshots_hash() {
236            result.insert("state_snapshots_hash".to_string(), state_hash.into());
237        }
238
239        result.into()
240    }
241
242    fn to_regression_json(&self, path: &str) -> std::io::Result<()> {
243        // Use the existing regression generate method
244        self.regression.generate(path)
245    }
246}
247
248impl TridentFuzzingData {
249    pub fn _merge(&mut self, other: TridentFuzzingData) {
250        self.metrics.merge_from(&other.metrics);
251        self.regression.merge_from(&other.regression);
252    }
253
254    pub fn get_exit_code(&self) -> i32 {
255        self.metrics.get_exit_code()
256    }
257}