summaryrefslogtreecommitdiff
path: root/cli/lsp/performance.rs
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-01-27 11:32:49 +1100
committerGitHub <noreply@github.com>2021-01-27 11:32:49 +1100
commite7323002d9fe5ab834754592916aba7cb842613d (patch)
tree0c7265e4a880c0defabf71ca560d54a36d055c69 /cli/lsp/performance.rs
parentada43cc56ac2e337cc034f8052d7e5da61268c34 (diff)
feat(lsp): add performance measurements (#9209)
Diffstat (limited to 'cli/lsp/performance.rs')
-rw-r--r--cli/lsp/performance.rs171
1 files changed, 171 insertions, 0 deletions
diff --git a/cli/lsp/performance.rs b/cli/lsp/performance.rs
new file mode 100644
index 000000000..8668519c8
--- /dev/null
+++ b/cli/lsp/performance.rs
@@ -0,0 +1,171 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::serde::Deserialize;
+use deno_core::serde::Serialize;
+use std::collections::HashMap;
+use std::collections::VecDeque;
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::time::Duration;
+use std::time::Instant;
+
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PerformanceAverage {
+ pub name: String,
+ pub count: u32,
+ pub average_duration: u32,
+}
+
+/// A structure which serves as a start of a measurement span.
+#[derive(Debug)]
+pub struct PerformanceMark {
+ name: String,
+ count: u32,
+ start: Instant,
+}
+
+/// A structure which holds the information about the measured span.
+#[derive(Debug, Clone)]
+pub struct PerformanceMeasure {
+ pub name: String,
+ pub count: u32,
+ pub duration: Duration,
+}
+
+impl From<PerformanceMark> for PerformanceMeasure {
+ fn from(value: PerformanceMark) -> Self {
+ Self {
+ name: value.name,
+ count: value.count,
+ duration: value.start.elapsed(),
+ }
+ }
+}
+
+/// A simple structure for marking a start of something to measure the duration
+/// of and measuring that duration. Each measurement is identified by a string
+/// name and a counter is incremented each time a new measurement is marked.
+///
+/// The structure will limit the size of measurements to the most recent 1000,
+/// and will roll off when that limit is reached.
+#[derive(Debug)]
+pub struct Performance {
+ counts: Arc<Mutex<HashMap<String, u32>>>,
+ max_size: usize,
+ measures: Arc<Mutex<VecDeque<PerformanceMeasure>>>,
+}
+
+impl Default for Performance {
+ fn default() -> Self {
+ Self {
+ counts: Default::default(),
+ max_size: 1_000,
+ measures: Default::default(),
+ }
+ }
+}
+
+impl Performance {
+ /// Return the count and average duration of a measurement identified by name.
+ #[cfg(test)]
+ pub fn average(&self, name: &str) -> Option<(usize, Duration)> {
+ let mut items = Vec::new();
+ for measure in self.measures.lock().unwrap().iter() {
+ if measure.name == name {
+ items.push(measure.duration);
+ }
+ }
+ let len = items.len();
+
+ if len > 0 {
+ let average = items.into_iter().sum::<Duration>() / len as u32;
+ Some((len, average))
+ } else {
+ None
+ }
+ }
+
+ /// Return an iterator which provides the names, count, and average duration
+ /// of each measurement.
+ pub fn averages(&self) -> Vec<PerformanceAverage> {
+ let mut averages: HashMap<String, Vec<Duration>> = HashMap::new();
+ for measure in self.measures.lock().unwrap().iter() {
+ averages
+ .entry(measure.name.clone())
+ .or_default()
+ .push(measure.duration);
+ }
+ averages
+ .into_iter()
+ .map(|(k, d)| {
+ let a = d.clone().into_iter().sum::<Duration>() / d.len() as u32;
+ PerformanceAverage {
+ name: k,
+ count: d.len() as u32,
+ average_duration: a.as_millis() as u32,
+ }
+ })
+ .collect()
+ }
+
+ /// Marks the start of a measurement which returns a performance mark
+ /// structure, which is then passed to `.measure()` to finalize the duration
+ /// and add it to the internal buffer.
+ pub fn mark<S: AsRef<str>>(&self, name: S) -> PerformanceMark {
+ let name = name.as_ref();
+ let mut counts = self.counts.lock().unwrap();
+ let count = counts.entry(name.to_string()).or_insert(0);
+ *count += 1;
+ PerformanceMark {
+ name: name.to_string(),
+ count: *count,
+ start: Instant::now(),
+ }
+ }
+
+ /// A function which accepts a previously created performance mark which will
+ /// be used to finalize the duration of the span being measured, and add the
+ /// measurement to the internal buffer.
+ pub fn measure(&self, mark: PerformanceMark) {
+ let measure = PerformanceMeasure::from(mark);
+ let mut measures = self.measures.lock().unwrap();
+ measures.push_back(measure);
+ while measures.len() > self.max_size {
+ measures.pop_front();
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_average() {
+ let performance = Performance::default();
+ let mark1 = performance.mark("a");
+ let mark2 = performance.mark("a");
+ let mark3 = performance.mark("b");
+ performance.measure(mark2);
+ performance.measure(mark1);
+ performance.measure(mark3);
+ let (count, _) = performance.average("a").expect("should have had value");
+ assert_eq!(count, 2);
+ let (count, _) = performance.average("b").expect("should have had value");
+ assert_eq!(count, 1);
+ assert!(performance.average("c").is_none());
+ }
+
+ #[test]
+ fn test_averages() {
+ let performance = Performance::default();
+ let mark1 = performance.mark("a");
+ let mark2 = performance.mark("a");
+ performance.measure(mark2);
+ performance.measure(mark1);
+ let averages = performance.averages();
+ assert_eq!(averages.len(), 1);
+ assert_eq!(averages[0].count, 2);
+ }
+}