rocketmq_common/utils/
util_all.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18use std::env;
19use std::fs;
20use std::io;
21use std::net::IpAddr;
22use std::path::Path;
23use std::path::PathBuf;
24use std::time::Duration;
25use std::time::Instant;
26use std::time::SystemTime;
27use std::time::UNIX_EPOCH;
28
29use cheetah_string::CheetahString;
30use chrono::DateTime;
31use chrono::Datelike;
32use chrono::Local;
33use chrono::NaiveDateTime;
34use chrono::ParseError;
35use chrono::ParseResult;
36use chrono::TimeZone;
37use chrono::Timelike;
38use chrono::Utc;
39use local_ip_address::Error;
40use once_cell::sync::Lazy;
41use tracing::error;
42use tracing::info;
43
44use crate::common::mix_all::MULTI_PATH_SPLITTER;
45
46pub const YYYY_MM_DD_HH_MM_SS: &str = "%Y-%m-%d %H:%M:%S%";
47pub const YYYY_MM_DD_HH_MM_SS_SSS: &str = "%Y-%m-%d %H:%M:%S%.f";
48pub const YYYYMMDDHHMMSS: &str = "%Y%m%d%H%M%S%";
49
50const HEX_ARRAY: [char; 16] = [
51    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
52];
53
54pub fn compute_elapsed_time_milliseconds(begin_time: Instant) -> u64 {
55    let elapsed = begin_time.elapsed();
56    elapsed.as_millis() as u64
57}
58
59pub fn is_it_time_to_do(when: &str) -> bool {
60    let hours: Vec<&str> = when.split(";").collect();
61    if !hours.is_empty() {
62        let now = Local::now();
63        for hour in hours {
64            let now_hour: i32 = hour.parse().unwrap_or(0);
65            if now_hour == now.hour() as i32 {
66                return true;
67            }
68        }
69    }
70    false
71}
72
73/// Converts a timestamp in milliseconds to a human-readable string format.
74///
75/// The format is: yyyy-MM-dd HH:mm:ss,SSS
76///
77/// # Arguments
78///
79/// * `t` - Timestamp in milliseconds
80///
81/// # Returns
82///
83/// Formatted date-time string
84pub fn time_millis_to_human_string2(t: i64) -> String {
85    let dt: DateTime<Local> = Local.timestamp_millis_opt(t).unwrap();
86
87    format!(
88        "{:04}-{:02}-{:02} {:02}:{:02}:{:02},{:03}",
89        dt.year(),
90        dt.month(),
91        dt.day(),
92        dt.hour(),
93        dt.minute(),
94        dt.second(),
95        dt.timestamp_subsec_millis()
96    )
97}
98
99/// Converts a timestamp in milliseconds to a compact human-readable string format.
100///
101/// The format is: yyyyMMddHHmmss
102///
103/// # Arguments
104///
105/// * `t` - Timestamp in milliseconds
106///
107/// # Returns
108///
109/// Formatted date-time string
110pub fn time_millis_to_human_string3(t: i64) -> String {
111    let dt: DateTime<Local> = Local.timestamp_millis_opt(t).unwrap();
112
113    format!(
114        "{:04}{:02}{:02}{:02}{:02}{:02}",
115        dt.year(),
116        dt.month(),
117        dt.day(),
118        dt.hour(),
119        dt.minute(),
120        dt.second()
121    )
122}
123
124/// Converts a timestamp in milliseconds to a human-readable string format.
125///
126/// The format is: yyyyMMddHHmmssSSS (year, month, day, hour, minute, second, millisecond)
127///
128/// # Arguments
129///
130/// * `t` - Timestamp in milliseconds
131///
132/// # Returns
133///
134/// Formatted date-time string
135pub fn time_millis_to_human_string(t: i64) -> String {
136    let dt: DateTime<Local> = Local.timestamp_millis_opt(t).unwrap();
137
138    format!(
139        "{:04}{:02}{:02}{:02}{:02}{:02}{:03}",
140        dt.year(),
141        dt.month(),
142        dt.day(),
143        dt.hour(),
144        dt.minute(),
145        dt.second(),
146        dt.timestamp_subsec_millis()
147    )
148}
149
150pub fn is_path_exists(path: &str) -> bool {
151    Path::new(path).exists()
152}
153
154pub fn get_disk_partition_space_used_percent(path: &str) -> f64 {
155    if path.is_empty() {
156        error!(
157            "Error when measuring disk space usage, path is null or empty, path: {}",
158            path
159        );
160        return -1.0;
161    }
162
163    let path = Path::new(path);
164    if !path.exists() {
165        error!(
166            "Error when measuring disk space usage, file doesn't exist on this path: {}",
167            path.to_string_lossy()
168        );
169        return -1.0;
170    }
171
172    match fs::metadata(path) {
173        Ok(metadata) => {
174            let total_space = metadata.len();
175            if total_space > 0 {
176                match (fs::metadata(path), fs::metadata(path)) {
177                    (Ok(metadata1), Ok(metadata2)) => {
178                        let free_space = metadata1.len();
179                        let usable_space = metadata2.len();
180                        let used_space = total_space.saturating_sub(free_space);
181                        let entire_space = used_space + usable_space;
182                        let round_num = if used_space * 100 % entire_space != 0 {
183                            1
184                        } else {
185                            0
186                        };
187                        let result = used_space * 100 / entire_space + round_num;
188                        return result as f64 / 100.0;
189                    }
190                    (Err(e), _) | (_, Err(e)) => {
191                        error!(
192                            "Error when measuring disk space usage, got exception: {:?}",
193                            e
194                        );
195                        return -1.0;
196                    }
197                }
198            }
199        }
200        Err(e) => {
201            error!(
202                "Error when measuring disk space usage, got exception: {:?}",
203                e
204            );
205            return -1.0;
206        }
207    }
208
209    -1.0
210}
211
212pub fn bytes_to_string(src: &[u8]) -> String {
213    let mut hex_chars = Vec::with_capacity(src.len() * 2);
214    for &byte in src {
215        let v = byte as usize;
216        hex_chars.push(HEX_ARRAY[v >> 4]);
217        hex_chars.push(HEX_ARRAY[v & 0x0F]);
218    }
219    hex_chars.into_iter().collect()
220}
221
222pub fn write_int(buffer: &mut [char], pos: usize, value: i32) {
223    let mut current_pos = pos;
224    for move_bits in (0..=28).rev().step_by(4) {
225        buffer[current_pos] = HEX_ARRAY[((value >> move_bits) & 0xF) as usize];
226        current_pos += 1;
227    }
228}
229
230pub fn write_short(buffer: &mut [char], pos: usize, value: i16) {
231    let mut current_pos = pos;
232    for move_bits in (0..=12).rev().step_by(4) {
233        buffer[current_pos] = HEX_ARRAY[((value >> move_bits) & 0xF) as usize];
234        current_pos += 1;
235    }
236}
237
238pub fn string_to_bytes(hex_string: impl Into<String>) -> Option<Vec<u8>> {
239    let hex_string = hex_string.into();
240    if hex_string.is_empty() {
241        return None;
242    }
243
244    let hex_string = hex_string.to_uppercase();
245    let length = hex_string.len() / 2;
246    let mut bytes = Vec::<u8>::with_capacity(length);
247
248    for i in 0..length {
249        let pos = i * 2;
250        let byte = (char_to_byte(hex_string.chars().nth(pos)?) << 4)
251            | char_to_byte(hex_string.chars().nth(pos + 1)?);
252
253        bytes.push(byte);
254    }
255
256    Some(bytes)
257}
258
259/// Converts a hexadecimal character to its corresponding byte value.
260///
261/// # Arguments
262///
263/// * `c` - A character representing a hexadecimal digit (0-9, A-F).
264///
265/// # Returns
266///
267/// The byte value of the hexadecimal character. If the character is not a valid
268/// hexadecimal digit, returns 0.
269#[inline]
270fn char_to_byte(c: char) -> u8 {
271    match c {
272        '0'..='9' => c as u8 - b'0',
273        'A'..='F' => c as u8 - b'A' + 10,
274        _ => 0,
275    }
276}
277
278/// Converts an offset value to a zero-padded string of length 20.
279///
280/// # Arguments
281///
282/// * `offset` - A 64-bit unsigned integer representing the offset.
283///
284/// # Returns
285///
286/// A string representation of the offset, zero-padded to a length of 20 characters.
287///
288/// # Examples
289///
290/// ```rust
291/// use rocketmq_common::UtilAll::offset_to_file_name;
292/// assert_eq!(offset_to_file_name(123), "00000000000000000123");
293/// assert_eq!(offset_to_file_name(0), "00000000000000000000");
294/// ```
295#[inline]
296pub fn offset_to_file_name(offset: u64) -> String {
297    format!("{offset:020}")
298}
299
300pub fn ensure_dir_ok(dir_name: &str) {
301    if !dir_name.is_empty() {
302        let multi_path_splitter = MULTI_PATH_SPLITTER.as_str();
303        if dir_name.contains(multi_path_splitter) {
304            for dir in dir_name.trim().split(&multi_path_splitter) {
305                create_dir_if_not_exist(dir);
306            }
307        } else {
308            create_dir_if_not_exist(dir_name);
309        }
310    }
311}
312
313fn create_dir_if_not_exist(dir_name: &str) {
314    let path = Path::new(dir_name);
315    if !path.exists() {
316        match fs::create_dir_all(path) {
317            Ok(_) => info!("{} mkdir OK", dir_name),
318            Err(_) => info!("{} mkdir Failed", dir_name),
319        }
320    }
321}
322
323pub fn compute_next_minutes_time_millis() -> u64 {
324    let now = SystemTime::now();
325    let duration_since_epoch = now.duration_since(UNIX_EPOCH).unwrap();
326    let millis_since_epoch = duration_since_epoch.as_millis() as u64;
327
328    let millis_in_minute = 60 * 1000;
329    ((millis_since_epoch / millis_in_minute) + 1) * millis_in_minute
330}
331
332pub fn compute_next_morning_time_millis() -> u64 {
333    let now = Local::now();
334    let tomorrow = now.date_naive().succ_opt().unwrap();
335    let next_morning = Local
336        .with_ymd_and_hms(tomorrow.year(), tomorrow.month(), tomorrow.day(), 0, 0, 0)
337        .unwrap();
338    next_morning.timestamp_millis() as u64
339}
340
341pub fn delete_empty_directory<P: AsRef<Path>>(path: P) {
342    let path = path.as_ref();
343    if !path.exists() {
344        return;
345    }
346    if !path.is_dir() {
347        return;
348    }
349    match fs::read_dir(path) {
350        Ok(entries) => {
351            if entries.count() == 0 {
352                match fs::remove_dir(path) {
353                    Ok(_) => info!("delete empty directory, {}", path.display()),
354                    Err(e) => error!("Error deleting directory: {}", e),
355                }
356            }
357        }
358        Err(e) => error!("Error reading directory: {}", e),
359    }
360}
361
362pub fn get_ip() -> rocketmq_error::RocketMQResult<Vec<u8>> {
363    match local_ip_address::local_ip() {
364        Ok(value) => match value {
365            IpAddr::V4(ip) => Ok(ip.octets().to_vec()),
366            IpAddr::V6(ip) => Ok(ip.octets().to_vec()),
367        },
368        Err(_) => match local_ip_address::local_ipv6() {
369            Ok(value) => match value {
370                IpAddr::V4(ip) => Ok(ip.octets().to_vec()),
371                IpAddr::V6(ip) => Ok(ip.octets().to_vec()),
372            },
373            Err(value) => Err(rocketmq_error::RocketMQError::illegal_argument(format!(
374                "IP error: {}",
375                value
376            ))),
377        },
378    }
379}
380
381pub fn get_ip_str() -> CheetahString {
382    match local_ip_address::local_ip() {
383        Ok(value) => match value {
384            IpAddr::V4(ip) => CheetahString::from_string(ip.to_string()),
385            IpAddr::V6(ip) => CheetahString::from_string(ip.to_string()),
386        },
387        Err(_) => match local_ip_address::local_ipv6() {
388            Ok(value) => match value {
389                IpAddr::V4(ip) => CheetahString::from_string(ip.to_string()),
390                IpAddr::V6(ip) => CheetahString::from_string(ip.to_string()),
391            },
392            Err(value) => CheetahString::empty(),
393        },
394    }
395}
396
397pub fn parse_date(date: &str, pattern: &str) -> Option<NaiveDateTime> {
398    NaiveDateTime::parse_from_str(date, pattern).ok()
399}
400
401#[cfg(test)]
402mod tests {
403    use std::time::Instant;
404
405    use super::*;
406
407    #[test]
408    fn compute_elapsed_time_milliseconds_returns_correct_duration() {
409        let start = Instant::now();
410        std::thread::sleep(std::time::Duration::from_millis(100));
411        let elapsed = compute_elapsed_time_milliseconds(start);
412        assert!(elapsed >= 100);
413    }
414
415    #[test]
416    fn is_it_time_to_do_returns_true_when_current_hour_is_in_input() {
417        let current_hour = Local::now().hour();
418        assert!(is_it_time_to_do(&current_hour.to_string()));
419    }
420
421    #[test]
422    fn is_it_time_to_do_returns_false_when_current_hour_is_not_in_input() {
423        let current_hour = (Local::now().hour() + 1) % 24;
424        assert!(!is_it_time_to_do(&current_hour.to_string()));
425    }
426
427    #[test]
428    fn time_millis_to_human_string_formats_correctly() {
429        let timestamp = 1743239631601;
430        let expected = Local
431            .timestamp_millis_opt(timestamp)
432            .unwrap()
433            .format("%Y%m%d%H%M%S%3f")
434            .to_string();
435        assert_eq!(time_millis_to_human_string(timestamp), expected);
436    }
437
438    #[test]
439    fn is_path_exists_returns_true_for_existing_path() {
440        assert!(is_path_exists("."));
441    }
442
443    #[test]
444    fn is_path_exists_returns_false_for_non_existing_path() {
445        assert!(!is_path_exists("./non_existing_path"));
446    }
447
448    #[test]
449    fn bytes_to_string_converts_correctly() {
450        let bytes = [0x41, 0x42, 0x43];
451        assert_eq!(bytes_to_string(&bytes), "414243");
452    }
453
454    #[test]
455    fn offset_to_file_name_formats_correctly() {
456        assert_eq!(offset_to_file_name(123), "00000000000000000123");
457    }
458
459    #[test]
460    fn ensure_dir_ok_creates_directory_if_not_exists() {
461        let dir_name = "./test_dir";
462        ensure_dir_ok(dir_name);
463        assert!(is_path_exists(dir_name));
464        std::fs::remove_dir(dir_name).unwrap();
465    }
466
467    #[test]
468    fn test_compute_next_minutes_time_millis() {
469        let next_minute = compute_next_minutes_time_millis();
470        let now = SystemTime::now()
471            .duration_since(UNIX_EPOCH)
472            .unwrap()
473            .as_millis() as u64;
474
475        assert!(next_minute > now);
476        assert_eq!(next_minute % (60 * 1000), 0);
477    }
478
479    /*    #[test]
480    fn compute_next_morning_time_millis_returns_correct_time() {
481        let now = Local::now();
482        let next_morning = compute_next_morning_time_millis();
483        let expected_next_morning = Local
484            .ymd(now.year(), now.month(), now.day() + 1)
485            .and_hms(0, 0, 0)
486            .timestamp_millis();
487        assert_eq!(next_morning, expected_next_morning as u64);
488    }*/
489    #[test]
490    fn time_millis_to_human_string2_formats_correctly_with_valid_timestamp() {
491        use chrono::TimeZone;
492        use chrono::Utc;
493        let timestamp = 1625140800000;
494        let expected = Local
495            .timestamp_millis_opt(timestamp)
496            .unwrap()
497            .format("%Y-%m-%d %H:%M:%S,%3f")
498            .to_string();
499        assert_eq!(time_millis_to_human_string2(timestamp), expected);
500    }
501
502    #[test]
503    fn time_millis_to_human_string3_formats_correctly_with_valid_timestamp() {
504        let timestamp = 1625140800000;
505        let expect = Local
506            .timestamp_millis_opt(timestamp)
507            .unwrap()
508            .format("%Y%m%d%H%M%S")
509            .to_string();
510        assert_eq!(time_millis_to_human_string3(timestamp), expect);
511    }
512}