nvml_wrapper/struct_wrappers/
device.rs

1use crate::bitmasks::device::FbcFlags;
2use crate::enum_wrappers::device::{BridgeChip, EncoderType, FbcSessionType, SampleValueType};
3use crate::enums::device::{FirmwareVersion, SampleValue, UsedGpuMemory};
4use crate::error::{nvml_try, Bits, NvmlError};
5use crate::ffi::bindings::*;
6use crate::structs::device::FieldId;
7#[cfg(feature = "serde")]
8use serde_derive::{Deserialize, Serialize};
9use std::{
10    cmp::Ordering,
11    ffi::{CStr, CString},
12};
13use std::{
14    convert::{TryFrom, TryInto},
15    os::raw::c_char,
16};
17
18/// PCI information about a GPU device.
19// Checked against local
20// Tested
21#[derive(Debug, Clone, Eq, PartialEq, Hash)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23pub struct PciInfo {
24    /// The bus on which the device resides, 0 to 0xff.
25    pub bus: u32,
26    /// The PCI identifier.
27    pub bus_id: String,
28    /// The device's ID on the bus, 0 to 31.
29    pub device: u32,
30    /// The PCI domain on which the device's bus resides, 0 to 0xffff.
31    pub domain: u32,
32    /// The combined 16-bit device ID and 16-bit vendor ID.
33    pub pci_device_id: u32,
34    /**
35    The 32-bit Sub System Device ID.
36
37    Will always be `None` if this `PciInfo` was obtained from `NvLink.remote_pci_info()`.
38    NVIDIA says that the C field that this corresponds to "is not filled ... and
39    is indeterminate" when being returned from that specific call.
40
41    Will be `Some` in all other cases.
42    */
43    pub pci_sub_system_id: Option<u32>,
44}
45
46impl PciInfo {
47    /**
48    Try to create this struct from its C equivalent.
49
50    Passing `false` for `sub_sys_id_present` will set the `pci_sub_system_id`
51    field to `None`. See the field docs for more.
52
53    # Errors
54
55    * `Utf8Error`, if the string obtained from the C function is not valid Utf8
56    */
57    pub fn try_from(struct_: nvmlPciInfo_t, sub_sys_id_present: bool) -> Result<Self, NvmlError> {
58        unsafe {
59            let bus_id_raw = CStr::from_ptr(struct_.busId.as_ptr());
60
61            Ok(Self {
62                bus: struct_.bus,
63                bus_id: bus_id_raw.to_str()?.into(),
64                device: struct_.device,
65                domain: struct_.domain,
66                pci_device_id: struct_.pciDeviceId,
67                pci_sub_system_id: if sub_sys_id_present {
68                    Some(struct_.pciSubSystemId)
69                } else {
70                    None
71                },
72            })
73        }
74    }
75}
76
77impl TryInto<nvmlPciInfo_t> for PciInfo {
78    type Error = NvmlError;
79
80    /**
81    Convert this `PciInfo` back into its C equivalent.
82
83    # Errors
84
85    * `NulError`, if a nul byte was found in the bus_id (shouldn't occur?)
86    * `StringTooLong`, if `bus_id.len()` exceeded the length of
87    `NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE`. This should (?) only be able to
88    occur if the user modifies `bus_id` in some fashion. We return an error
89    rather than panicking.
90    */
91    fn try_into(self) -> Result<nvmlPciInfo_t, Self::Error> {
92        // This is more readable than spraying `buf_size as usize` everywhere
93        const fn buf_size() -> usize {
94            NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE as usize
95        }
96
97        let mut bus_id_c: [c_char; buf_size()] = [0; buf_size()];
98        let mut bus_id = CString::new(self.bus_id)?.into_bytes_with_nul();
99
100        // Make the string the same length as the array we need to clone it to
101        match bus_id.len().cmp(&buf_size()) {
102            Ordering::Less => {
103                while bus_id.len() != buf_size() {
104                    bus_id.push(0);
105                }
106            }
107            Ordering::Equal => {
108                // No need to do anything; the buffers are already the same length
109            }
110            Ordering::Greater => {
111                return Err(NvmlError::StringTooLong {
112                    max_len: buf_size(),
113                    actual_len: bus_id.len(),
114                })
115            }
116        }
117
118        bus_id_c.clone_from_slice(&bus_id.into_iter().map(|b| b as c_char).collect::<Vec<_>>());
119
120        Ok(nvmlPciInfo_t {
121            busIdLegacy: [0; NVML_DEVICE_PCI_BUS_ID_BUFFER_V2_SIZE as usize],
122            domain: self.domain,
123            bus: self.bus,
124            device: self.device,
125            pciDeviceId: self.pci_device_id,
126            // This seems the most correct thing to do? Since this should only
127            // be none if obtained from `NvLink.remote_pci_info()`.
128            pciSubSystemId: self.pci_sub_system_id.unwrap_or(0),
129            busId: bus_id_c,
130        })
131    }
132}
133
134/// BAR1 memory allocation information for a device (in bytes)
135// Checked against local
136#[derive(Debug, Clone, Eq, PartialEq, Hash)]
137#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
138pub struct BAR1MemoryInfo {
139    /// Unallocated
140    pub free: u64,
141    /// Total memory
142    pub total: u64,
143    /// Allocated
144    pub used: u64,
145}
146
147impl From<nvmlBAR1Memory_t> for BAR1MemoryInfo {
148    fn from(struct_: nvmlBAR1Memory_t) -> Self {
149        Self {
150            free: struct_.bar1Free,
151            total: struct_.bar1Total,
152            used: struct_.bar1Used,
153        }
154    }
155}
156
157/// Information about a bridge chip.
158// Checked against local
159#[derive(Debug, Clone, Eq, PartialEq, Hash)]
160#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
161pub struct BridgeChipInfo {
162    pub fw_version: FirmwareVersion,
163    pub chip_type: BridgeChip,
164}
165
166impl TryFrom<nvmlBridgeChipInfo_t> for BridgeChipInfo {
167    type Error = NvmlError;
168
169    /**
170    Construct `BridgeChipInfo` from the corresponding C struct.
171
172    # Errors
173
174    * `UnexpectedVariant`, for which you can read the docs for
175    */
176    fn try_from(value: nvmlBridgeChipInfo_t) -> Result<Self, Self::Error> {
177        let fw_version = FirmwareVersion::from(value.fwVersion);
178        let chip_type = BridgeChip::try_from(value.type_)?;
179
180        Ok(Self {
181            fw_version,
182            chip_type,
183        })
184    }
185}
186
187/**
188This struct stores the complete hierarchy of the bridge chip within the board.
189
190The immediate bridge is stored at index 0 of `chips_hierarchy`. The parent to
191the immediate bridge is at index 1, and so forth.
192*/
193// Checked against local
194#[derive(Debug, Clone, Eq, PartialEq, Hash)]
195#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
196pub struct BridgeChipHierarchy {
197    /// Hierarchy of bridge chips on the board.
198    pub chips_hierarchy: Vec<BridgeChipInfo>,
199    /// Number of bridge chips on the board.
200    pub chip_count: u8,
201}
202
203impl TryFrom<nvmlBridgeChipHierarchy_t> for BridgeChipHierarchy {
204    type Error = NvmlError;
205
206    /**
207    Construct `BridgeChipHierarchy` from the corresponding C struct.
208
209    # Errors
210
211    * `UnexpectedVariant`, for which you can read the docs for
212    */
213    fn try_from(value: nvmlBridgeChipHierarchy_t) -> Result<Self, Self::Error> {
214        let chips_hierarchy = value
215            .bridgeChipInfo
216            .iter()
217            .map(|bci| BridgeChipInfo::try_from(*bci))
218            .collect::<Result<_, NvmlError>>()?;
219
220        Ok(Self {
221            chips_hierarchy,
222            chip_count: value.bridgeCount,
223        })
224    }
225}
226
227/// Information about compute processes running on the GPU.
228// Checked against local
229#[derive(Debug, Clone, Eq, PartialEq, Hash)]
230#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
231pub struct ProcessInfo {
232    // Process ID.
233    pub pid: u32,
234    /// Amount of used GPU memory in bytes.
235    pub used_gpu_memory: UsedGpuMemory,
236    /// The ID of the GPU instance this process is running on, if applicable.
237    ///
238    /// MIG (Multi-Instance GPU) must be enabled on the device for this field
239    /// to be set.
240    pub gpu_instance_id: Option<u32>,
241    /// The ID of the compute instance this process is running on, if applicable.
242    ///
243    /// MIG (Multi-Instance GPU) must be enabled on the device for this field
244    /// to be set.
245    pub compute_instance_id: Option<u32>,
246}
247
248impl From<nvmlProcessInfo_t> for ProcessInfo {
249    fn from(struct_: nvmlProcessInfo_t) -> Self {
250        const NO_VALUE: u32 = 0xFFFFFFFF;
251
252        let gpu_instance_id = Some(struct_.gpuInstanceId).filter(|id| *id != NO_VALUE);
253        let compute_instance_id = Some(struct_.computeInstanceId).filter(|id| *id != NO_VALUE);
254
255        Self {
256            pid: struct_.pid,
257            used_gpu_memory: UsedGpuMemory::from(struct_.usedGpuMemory),
258            gpu_instance_id,
259            compute_instance_id,
260        }
261    }
262}
263
264/// Detailed ECC error counts for a device.
265// Checked against local
266#[derive(Debug, Clone, Eq, PartialEq, Hash)]
267#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
268pub struct EccErrorCounts {
269    pub device_memory: u64,
270    pub l1_cache: u64,
271    pub l2_cache: u64,
272    pub register_file: u64,
273}
274
275impl From<nvmlEccErrorCounts_t> for EccErrorCounts {
276    fn from(struct_: nvmlEccErrorCounts_t) -> Self {
277        Self {
278            device_memory: struct_.deviceMemory,
279            l1_cache: struct_.l1Cache,
280            l2_cache: struct_.l2Cache,
281            register_file: struct_.registerFile,
282        }
283    }
284}
285
286/// Memory allocation information for a device (in bytes).
287// Checked against local
288#[derive(Debug, Clone, Eq, PartialEq, Hash)]
289#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
290pub struct MemoryInfo {
291    /// Unallocated FB memory.
292    pub free: u64,
293    /// Total installed FB memory.
294    pub total: u64,
295    /// Allocated FB memory.
296    ///
297    /// Note that the driver/GPU always sets aside a small amount of memory for
298    /// bookkeeping.
299    pub used: u64,
300}
301
302impl From<nvmlMemory_t> for MemoryInfo {
303    fn from(struct_: nvmlMemory_t) -> Self {
304        Self {
305            free: struct_.free,
306            total: struct_.total,
307            used: struct_.used,
308        }
309    }
310}
311
312/// Utilization information for a device. Each sample period may be between 1
313/// second and 1/6 second, depending on the product being queried.
314// Checked against local
315#[derive(Debug, Clone, Eq, PartialEq, Hash)]
316#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
317pub struct Utilization {
318    /// Percent of time over the past sample period during which one or more
319    /// kernels was executing on the GPU.
320    pub gpu: u32,
321    /// Percent of time over the past sample period during which global (device)
322    /// memory was being read or written to.
323    pub memory: u32,
324}
325
326impl From<nvmlUtilization_t> for Utilization {
327    fn from(struct_: nvmlUtilization_t) -> Self {
328        Self {
329            gpu: struct_.gpu,
330            memory: struct_.memory,
331        }
332    }
333}
334
335/// Performance policy violation status data.
336// Checked against local
337#[derive(Debug, Clone, Eq, PartialEq, Hash)]
338#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
339pub struct ViolationTime {
340    /// Represents CPU timestamp in microseconds.
341    pub reference_time: u64,
342    /// Violation time in nanoseconds.
343    pub violation_time: u64,
344}
345
346impl From<nvmlViolationTime_t> for ViolationTime {
347    fn from(struct_: nvmlViolationTime_t) -> Self {
348        Self {
349            reference_time: struct_.referenceTime,
350            violation_time: struct_.violationTime,
351        }
352    }
353}
354
355/**
356Accounting statistics for a process.
357
358There is a field: `unsigned int reserved[5]` present on the C struct that this wraps
359that NVIDIA says is "reserved for future use." If it ever gets used in the future,
360an equivalent wrapping field will have to be added to this struct.
361*/
362// Checked against local
363#[derive(Debug, Clone, Eq, PartialEq, Hash)]
364#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
365pub struct AccountingStats {
366    /**
367    Percent of time over the process's lifetime during which one or more kernels was
368    executing on the GPU. This is just like what is returned by
369    `Device.utilization_rates()` except it is for the lifetime of a process (not just
370    the last sample period).
371
372    It will be `None` if `Device.utilization_rates()` is not supported.
373    */
374    pub gpu_utilization: Option<u32>,
375    /// Whether the process is running.
376    pub is_running: bool,
377    /// Max total memory in bytes that was ever allocated by the process.
378    ///
379    /// It will be `None` if `ProcessInfo.used_gpu_memory` is not supported.
380    pub max_memory_usage: Option<u64>,
381    /**
382    Percent of time over the process's lifetime during which global (device) memory
383    was being read from or written to.
384
385    It will be `None` if `Device.utilization_rates()` is not supported.
386    */
387    pub memory_utilization: Option<u32>,
388    /// CPU timestamp in usec representing the start time for the process.
389    pub start_time: u64,
390    /// Amount of time in ms during which the compute context was active. This
391    /// will be zero if the process is not terminated.
392    pub time: u64,
393}
394
395impl From<nvmlAccountingStats_t> for AccountingStats {
396    fn from(struct_: nvmlAccountingStats_t) -> Self {
397        let not_avail_u64 = (NVML_VALUE_NOT_AVAILABLE) as u64;
398        let not_avail_u32 = (NVML_VALUE_NOT_AVAILABLE) as u32;
399
400        #[allow(clippy::match_like_matches_macro)]
401        Self {
402            gpu_utilization: match struct_.gpuUtilization {
403                v if v == not_avail_u32 => None,
404                _ => Some(struct_.gpuUtilization),
405            },
406            is_running: match struct_.isRunning {
407                0 => false,
408                // NVIDIA only says 1 is for running, but I don't think anything
409                // else warrants an error (or a panic), so
410                _ => true,
411            },
412            max_memory_usage: match struct_.maxMemoryUsage {
413                v if v == not_avail_u64 => None,
414                _ => Some(struct_.maxMemoryUsage),
415            },
416            memory_utilization: match struct_.memoryUtilization {
417                v if v == not_avail_u32 => None,
418                _ => Some(struct_.memoryUtilization),
419            },
420            start_time: struct_.startTime,
421            time: struct_.time,
422        }
423    }
424}
425
426/// Holds encoder session information.
427#[derive(Debug, Clone, Eq, PartialEq, Hash)]
428#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
429pub struct EncoderSessionInfo {
430    /// Unique ID for this session.
431    pub session_id: u32,
432    /// The ID of the process that owns this session.
433    pub pid: u32,
434    /// The ID of the vGPU instance that owns this session (if applicable).
435    // TODO: Stronger typing if vgpu stuff gets wrapped
436    pub vgpu_instance: Option<u32>,
437    pub codec_type: EncoderType,
438    /// Current horizontal encoding resolution.
439    pub hres: u32,
440    /// Current vertical encoding resolution.
441    pub vres: u32,
442    /// Moving average encode frames per second.
443    pub average_fps: u32,
444    /// Moving average encode latency in μs.
445    pub average_latency: u32,
446}
447
448impl TryFrom<nvmlEncoderSessionInfo_t> for EncoderSessionInfo {
449    type Error = NvmlError;
450
451    /**
452    Construct `EncoderSessionInfo` from the corresponding C struct.
453
454    # Errors
455
456    * `UnexpectedVariant`, for which you can read the docs for
457    */
458    fn try_from(value: nvmlEncoderSessionInfo_t) -> Result<Self, Self::Error> {
459        Ok(Self {
460            session_id: value.sessionId,
461            pid: value.pid,
462            vgpu_instance: match value.vgpuInstance {
463                0 => None,
464                other => Some(other),
465            },
466            codec_type: EncoderType::try_from(value.codecType)?,
467            hres: value.hResolution,
468            vres: value.vResolution,
469            average_fps: value.averageFps,
470            average_latency: value.averageLatency,
471        })
472    }
473}
474
475/// Sample info.
476// Checked against local
477#[derive(Debug, Clone, PartialEq)]
478#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
479pub struct Sample {
480    /// CPU timestamp in μs
481    pub timestamp: u64,
482    pub value: SampleValue,
483}
484
485impl Sample {
486    /// Given a tag and an untagged union, returns a Rust enum with the correct
487    /// union variant.
488    pub fn from_tag_and_struct(tag: &SampleValueType, struct_: nvmlSample_t) -> Self {
489        Self {
490            timestamp: struct_.timeStamp,
491            value: SampleValue::from_tag_and_union(tag, struct_.sampleValue),
492        }
493    }
494}
495
496#[derive(Debug, Clone, Eq, PartialEq, Hash)]
497#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
498pub struct ProcessUtilizationSample {
499    pub pid: u32,
500    /// CPU timestamp in μs
501    pub timestamp: u64,
502    /// SM (3D / compute) utilization
503    pub sm_util: u32,
504    /// Frame buffer memory utilization
505    pub mem_util: u32,
506    /// Encoder utilization
507    pub enc_util: u32,
508    /// Decoder utilization
509    pub dec_util: u32,
510}
511
512impl From<nvmlProcessUtilizationSample_t> for ProcessUtilizationSample {
513    fn from(struct_: nvmlProcessUtilizationSample_t) -> Self {
514        Self {
515            pid: struct_.pid,
516            timestamp: struct_.timeStamp,
517            sm_util: struct_.smUtil,
518            mem_util: struct_.memUtil,
519            enc_util: struct_.encUtil,
520            dec_util: struct_.decUtil,
521        }
522    }
523}
524
525/// Struct that stores information returned from `Device.field_values_for()`.
526// TODO: Missing a lot of derives because of the `Result`
527#[derive(Debug)]
528pub struct FieldValueSample {
529    /// The field that this sample is for.
530    pub field: FieldId,
531    /// This sample's CPU timestamp in μs (Unix time).
532    pub timestamp: i64,
533    /**
534    How long this field value took to update within NVML, in μs.
535
536    This value may be averaged across several fields serviced by the same
537    driver call.
538    */
539    pub latency: i64,
540    /// The value of this sample.
541    ///
542    /// Will be an error if retrieving this specific value failed.
543    pub value: Result<SampleValue, NvmlError>,
544}
545
546impl TryFrom<nvmlFieldValue_t> for FieldValueSample {
547    type Error = NvmlError;
548
549    /**
550    Construct `FieldValueSample` from the corresponding C struct.
551
552    # Errors
553
554    * `UnexpectedVariant`, for which you can read the docs for
555    */
556    fn try_from(value: nvmlFieldValue_t) -> Result<Self, Self::Error> {
557        Ok(Self {
558            field: FieldId(value.fieldId),
559            timestamp: value.timestamp,
560            latency: value.latencyUsec,
561            value: match nvml_try(value.nvmlReturn) {
562                Ok(_) => Ok(SampleValue::from_tag_and_union(
563                    &SampleValueType::try_from(value.valueType)?,
564                    value.value,
565                )),
566                Err(e) => Err(e),
567            },
568        })
569    }
570}
571
572/// Holds global frame buffer capture session statistics.
573#[derive(Debug, Clone, Eq, PartialEq)]
574#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
575pub struct FbcStats {
576    /// The total number of sessions
577    pub sessions_count: u32,
578    /// Moving average of new frames captured per second for all capture sessions
579    pub average_fps: u32,
580    /// Moving average of new frame capture latency in microseconds for all capture sessions
581    pub average_latency: u32,
582}
583
584impl From<nvmlFBCStats_t> for FbcStats {
585    fn from(struct_: nvmlFBCStats_t) -> Self {
586        Self {
587            sessions_count: struct_.sessionsCount,
588            average_fps: struct_.averageFPS,
589            average_latency: struct_.averageLatency,
590        }
591    }
592}
593
594/// Information about a frame buffer capture session.
595#[derive(Debug, Clone, Eq, PartialEq)]
596#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
597pub struct FbcSessionInfo {
598    /// Unique session ID
599    pub session_id: u32,
600    /// The ID of the process that owns this session
601    pub pid: u32,
602    /// The ID of the vGPU instance that owns this session (if applicable).
603    // TODO: Stronger typing if vgpu stuff gets wrapped
604    pub vgpu_instance: Option<u32>,
605    /// The identifier of the display this session is running on
606    pub display_ordinal: u32,
607    /// The type of this session
608    pub session_type: FbcSessionType,
609    /// Various flags with info
610    pub session_flags: FbcFlags,
611    /// The maximum horizontal resolution supported by this session
612    pub hres_max: u32,
613    /// The maximum vertical resolution supported by this session
614    pub vres_max: u32,
615    /// The horizontal resolution requested by the caller in the capture call
616    pub hres: u32,
617    /// The vertical resolution requested by the caller in the capture call
618    pub vres: u32,
619    /// Moving average of new frames captured per second for this session
620    pub average_fps: u32,
621    /// Moving average of new frame capture latency in microseconds for this session
622    pub average_latency: u32,
623}
624
625impl TryFrom<nvmlFBCSessionInfo_t> for FbcSessionInfo {
626    type Error = NvmlError;
627
628    /**
629    Construct `FbcSessionInfo` from the corresponding C struct.
630
631    # Errors
632
633    * `UnexpectedVariant`, for which you can read the docs for
634    * `IncorrectBits`, if the `sessionFlags` from the given struct do match the
635    wrapper definition
636    */
637    fn try_from(value: nvmlFBCSessionInfo_t) -> Result<Self, Self::Error> {
638        Ok(Self {
639            session_id: value.sessionId,
640            pid: value.pid,
641            vgpu_instance: match value.vgpuInstance {
642                0 => None,
643                other => Some(other),
644            },
645            display_ordinal: value.displayOrdinal,
646            session_type: FbcSessionType::try_from(value.sessionType)?,
647            session_flags: FbcFlags::from_bits(value.sessionFlags)
648                .ok_or(NvmlError::IncorrectBits(Bits::U32(value.sessionFlags)))?,
649            hres_max: value.hMaxResolution,
650            vres_max: value.vMaxResolution,
651            hres: value.hResolution,
652            vres: value.vResolution,
653            average_fps: value.averageFPS,
654            average_latency: value.averageLatency,
655        })
656    }
657}
658
659#[cfg(test)]
660#[allow(unused_variables, unused_imports)]
661mod tests {
662    use crate::error::*;
663    use crate::ffi::bindings::*;
664    use crate::test_utils::*;
665    use std::convert::TryInto;
666    use std::mem;
667
668    #[test]
669    fn pci_info_from_to_c() {
670        let nvml = nvml();
671        test_with_device(3, &nvml, |device| {
672            let converted: nvmlPciInfo_t = device
673                .pci_info()
674                .expect("wrapped pci info")
675                .try_into()
676                .expect("converted c pci info");
677
678            let sym = nvml_sym(nvml.lib.nvmlDeviceGetPciInfo_v3.as_ref())?;
679
680            let raw = unsafe {
681                let mut pci_info: nvmlPciInfo_t = mem::zeroed();
682                nvml_try(sym(device.handle(), &mut pci_info)).expect("raw pci info");
683                pci_info
684            };
685
686            assert_eq!(converted.busId, raw.busId);
687            assert_eq!(converted.domain, raw.domain);
688            assert_eq!(converted.bus, raw.bus);
689            assert_eq!(converted.device, raw.device);
690            assert_eq!(converted.pciDeviceId, raw.pciDeviceId);
691            assert_eq!(converted.pciSubSystemId, raw.pciSubSystemId);
692
693            Ok(())
694        })
695    }
696}