diff --git a/src/flamegraph/flamegraph.css b/src/flamegraph/flamegraph.css index 42e9d535..aa6bcb0a 100644 --- a/src/flamegraph/flamegraph.css +++ b/src/flamegraph/flamegraph.css @@ -1,6 +1,8 @@ #matched { text-anchor:end; } #search { text-anchor:end; opacity:0.1; cursor:pointer; } #search:hover, #search.show { opacity:1; } +#ignorecase { text-anchor:end; opacity:0.1; cursor:pointer; } +#ignorecase:hover, #ignorecase.show { opacity:1; } #subtitle { text-anchor:middle; font-color:rgb(160,160,160); } #unzoom { cursor:pointer; } #frames > *:hover { stroke:black; stroke-width:0.5; cursor:pointer; } diff --git a/src/flamegraph/flamegraph.js b/src/flamegraph/flamegraph.js index 7c8e41e1..8ccb6a71 100644 --- a/src/flamegraph/flamegraph.js +++ b/src/flamegraph/flamegraph.js @@ -1,8 +1,9 @@ "use strict"; -var details, searchbtn, unzoombtn, matchedtxt, svg, searching, frames, known_font_width; +var details, searchbtn, ignorecasebtn, unzoombtn, matchedtxt, svg, searching, frames, known_font_width, case_insensitive, current_search_term; function init(evt) { details = document.getElementById("details").firstChild; searchbtn = document.getElementById("search"); + ignorecasebtn = document.getElementById("ignorecase"); unzoombtn = document.getElementById("unzoom"); matchedtxt = document.getElementById("matched"); svg = document.getElementsByTagName("svg")[0]; @@ -10,14 +11,22 @@ function init(evt) { known_font_width = get_monospace_width(frames); total_samples = parseInt(frames.attributes.total_samples.value); searching = 0; + case_insensitive = 0; + current_search_term = null; // Use GET parameters to restore a flamegraph's state. var restore_state = function() { var params = get_params(); if (params.x && params.y) zoom(find_group(document.querySelector('[*|x="' + params.x + '"][y="' + params.y + '"]'))); - if (params.s) + if (params.s) { + if (params.ic) { + case_insensitive = 1; + ignorecasebtn.classList.add("show"); + } + current_search_term = params.s; search(params.s); + } }; if (fluiddrawing) { @@ -41,7 +50,8 @@ function init(evt) { // Keep search elements at a fixed distance from right edge. var svgWidth = svg.width.baseVal.value; - searchbtn.attributes.x.value = svgWidth - xpad; + ignorecasebtn.attributes.x.value = svgWidth - xpad; + searchbtn.attributes.x.value = svgWidth - xpad - fontsize * fontwidth * 4; matchedtxt.attributes.x.value = svgWidth - xpad; }; window.addEventListener('resize', function() { @@ -87,6 +97,7 @@ window.addEventListener("click", function(e) { history.replaceState(null, null, parse_params(params)); } else if (e.target.id == "search") search_prompt(); + else if (e.target.id == "ignorecase") toggle_ignorecase(); }, false) // mouse-over for info // show @@ -106,6 +117,13 @@ window.addEventListener("keydown",function (e) { search_prompt(); } }, false) +// ctrl-I to toggle case-insensitive search +window.addEventListener("keydown",function (e) { + if (e.ctrlKey && e.keyCode === 73) { + e.preventDefault(); + toggle_ignorecase(); + } +}, false) // functions function get_params() { var params = {}; @@ -371,18 +389,22 @@ function reset_search() { } var params = get_params(); delete params.s; + delete params.ic; history.replaceState(null, null, parse_params(params)); } function search_prompt() { if (!searching) { + var casemsg = case_insensitive ? ", ignoring case" : ""; var term = prompt("Enter a search term (regexp " + - "allowed, eg: ^ext4_)", ""); + "allowed, eg: ^ext4_)" + casemsg + "\nPress Ctrl+i to toggle case sensitivity", ""); if (term != null) { + current_search_term = term; search(term) } } else { reset_search(); searching = 0; + current_search_term = null; searchbtn.classList.remove("show"); searchbtn.firstChild.nodeValue = "Search" matchedtxt.classList.add("hide"); @@ -390,7 +412,7 @@ function search_prompt() { } } function search(term) { - var re = new RegExp(term); + var re = new RegExp(term, case_insensitive ? "i" : ""); var el = frames.children; var matches = new Object(); var maxwidth = 0; @@ -425,10 +447,20 @@ function search(term) { searching = 1; } } - if (!searching) + if (!searching) { + searchbtn.classList.remove("show"); + searchbtn.firstChild.nodeValue = "Search"; + matchedtxt.classList.add("hide"); + matchedtxt.firstChild.nodeValue = ""; return; + } var params = get_params(); params.s = term; + if (case_insensitive) { + params.ic = "1"; + } else { + delete params.ic; + } history.replaceState(null, null, parse_params(params)); searchbtn.classList.add("show"); @@ -465,6 +497,19 @@ function search(term) { if (pct != 100) pct = pct.toFixed(1); matchedtxt.firstChild.nodeValue = "Matched: " + pct + "%"; } +function toggle_ignorecase() { + case_insensitive = !case_insensitive; + if (case_insensitive) { + ignorecasebtn.classList.add("show"); + } else { + ignorecasebtn.classList.remove("show"); + } + if (current_search_term != null) { + reset_search(); + searching = 0; + search(current_search_term); + } +} function format_percent(n) { return n.toFixed(4) + "%"; } diff --git a/src/flamegraph/svg.rs b/src/flamegraph/svg.rs index 23d3924a..676fc77a 100644 --- a/src/flamegraph/svg.rs +++ b/src/flamegraph/svg.rs @@ -232,11 +232,30 @@ text {{ font-family:{}; font-size:{}px }} }, )?; + { + let x = write!(buf, "{:.2}", image_width as usize - super::XPAD); + let y = write!(buf, "{:.2}", (opt.font_size * 2) as f64); + svg.write_event(Event::Start(BytesStart::new("text").with_attributes( + args!( + "id" => "ignorecase", + "fill" => &*style_options.uicolor, + "x" => &buf[x], + "y" => &buf[y] + ), + )))?; + svg.write_event(Event::Start(BytesStart::new("title")))?; + svg.write_event(Event::Text(BytesText::new("ignore case")))?; + svg.write_event(Event::End(BytesEnd::new("title")))?; + svg.write_event(Event::Text(BytesText::new("ic")))?; + svg.write_event(Event::End(BytesEnd::new("text")))?; + } + + let ic_offset = (opt.font_size as f64 * opt.font_width * 4.0) as usize; write_str( svg, &mut buf, TextItem { - x: Dimension::Pixels(image_width as usize - super::XPAD), + x: Dimension::Pixels(image_width as usize - super::XPAD - ic_offset), y: (opt.font_size * 2) as f64, text: "Search".into(), extra: vec![("id", "search"), ("fill", &style_options.uicolor)],