| /* Copyright (C) 2021-2024 Free Software Foundation, Inc. |
| Contributed by Oracle. |
| |
| This file is part of GNU Binutils. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, 51 Franklin Street - Fifth Floor, Boston, |
| MA 02110-1301, USA. */ |
| |
| #include "config.h" |
| #include <ctype.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include "Filter.h" |
| #include "util.h" |
| #include "i18n.h" |
| #include "data_pckts.h" |
| #include "StringBuilder.h" |
| #include "Experiment.h" |
| |
| |
| // ======================================================================== |
| // Subclass: FilterNumeric |
| // Public Methods |
| |
| FilterNumeric::FilterNumeric (Experiment *_exp, const char *_cmd, |
| const char *_name) |
| { |
| exp = _exp; |
| cmd = dbe_strdup (_cmd); |
| name = dbe_strdup (_name); |
| pattern = NULL; |
| status = NULL; |
| items = NULL; |
| prop_name = NULL; |
| first = (uint64_t) - 1; |
| last = (uint64_t) - 1; |
| nselected = 0; |
| nitems = 0; |
| } |
| |
| FilterNumeric::~FilterNumeric () |
| { |
| free (cmd); |
| free (name); |
| free (pattern); |
| free (status); |
| Destroy (items); |
| } |
| |
| // sets min and max for this filter; should be called when the range is |
| // known -- that comes after the first PathTree build, in the current |
| // sequence of things |
| void |
| FilterNumeric::set_range (uint64_t findex, uint64_t lindex, uint64_t total) |
| { |
| if (first == findex && last == lindex) |
| return; |
| first = findex; |
| last = lindex; |
| nitems = total; |
| nselected = nitems; |
| if (pattern) |
| { |
| free (pattern); |
| pattern = NULL; |
| } |
| if (status) |
| { |
| free (status); |
| status = NULL; |
| } |
| } |
| |
| void |
| FilterNumeric::update_range () |
| { |
| if (exp == NULL) |
| return; |
| if (streq (cmd, NTXT ("sample"))) |
| set_range (1, (uint64_t) exp->nsamples (), exp->nsamples ()); |
| else if (streq (cmd, NTXT ("thread"))) |
| set_range (exp->min_thread, exp->max_thread, exp->thread_cnt); |
| else if (streq (cmd, NTXT ("LWP"))) |
| set_range (exp->min_lwp, exp->max_lwp, exp->lwp_cnt); |
| else if (streq (cmd, NTXT ("cpu"))) |
| { |
| if (exp->min_cpu != (uint64_t) - 1) |
| set_range (exp->min_cpu, exp->max_cpu, exp->cpu_cnt); |
| } |
| } |
| |
| // get_advanced_filter -- returns a string matching the current setting |
| char * |
| FilterNumeric::get_advanced_filter () |
| { |
| if (items == NULL) |
| return NULL; |
| if (items->size () == 0) |
| return dbe_strdup (NTXT ("0")); |
| |
| StringBuilder sb; |
| if (items->size () > 1) |
| sb.append ('('); |
| for (int i = 0; i < items->size (); i++) |
| { |
| RangePair *rp = items->fetch (i); |
| if (i > 0) |
| sb.append (NTXT (" || ")); |
| sb.append ('('); |
| sb.append (prop_name); |
| if (rp->first == rp->last) |
| { |
| sb.append (NTXT ("==")); |
| sb.append ((long long) rp->first); |
| } |
| else |
| { |
| sb.append (NTXT (">=")); |
| sb.append ((long long) rp->first); |
| sb.append (NTXT (" && ")); |
| sb.append (prop_name); |
| sb.append (NTXT ("<=")); |
| sb.append ((long long) rp->last); |
| } |
| sb.append (')'); |
| } |
| if (items->size () > 1) |
| sb.append (')'); |
| return sb.toString (); |
| } |
| |
| |
| // get_pattern -- returns a string matching the current setting |
| |
| char * |
| FilterNumeric::get_pattern () |
| { |
| update_range (); |
| if (pattern) |
| return pattern; |
| StringBuilder sb; |
| if (items == NULL) |
| { |
| if (last == (uint64_t) - 1 && last == first) |
| // neither set; data not available |
| sb.append (GTXT ("(data not recorded)")); |
| else |
| sb.append (GTXT ("all")); |
| } |
| else if (items->size () == 0) |
| sb.append (GTXT ("none")); |
| else |
| { |
| for (int i = 0; i < items->size (); i++) |
| { |
| RangePair *rp = items->fetch (i); |
| if (i > 0) |
| sb.append (','); |
| sb.append ((long long) rp->first); |
| if (rp->first != rp->last) |
| { |
| sb.append ('-'); |
| sb.append ((long long) rp->last); |
| } |
| } |
| } |
| pattern = sb.toString (); |
| return pattern; |
| } |
| |
| char * |
| FilterNumeric::get_status () |
| { |
| update_range (); |
| if (status == NULL) |
| update_status (); |
| return dbe_strdup (status); |
| } |
| |
| // set_pattern -- set the filter to a new pattern |
| // set error true/false if there was or was not an error parsing string |
| // Returns true/false if the filter changed, implying a rebuild of data |
| bool |
| FilterNumeric::set_pattern (char *str, bool *error) |
| { |
| update_range (); |
| // save the old filter |
| Vector<RangePair *> *olditems = items; |
| *error = false; |
| if (strcmp (str, NTXT ("all")) == 0) |
| // if all, leave items NULL |
| items = NULL; |
| else if (strcmp (str, NTXT ("none")) == 0) |
| // if none, leave items as a zero-length vector |
| items = new Vector<RangePair *>(0); |
| else |
| { |
| uint64_t val, val2; |
| char *s = str; |
| char *nexts = s; |
| items = NULL; |
| for (bool done = false; done == false;) |
| { |
| // tokenize the string |
| // Does it start with a "-" ? |
| if (*nexts == '-') |
| val = first; // yes, set val to first, and see what follows |
| else |
| { |
| // it must start with a number |
| val = get_next_number (s, &nexts, error); |
| if (*error == true) |
| break; |
| } |
| |
| // look at the next character |
| switch (*nexts) |
| { |
| case ',': |
| s = ++nexts; |
| *error = include_range (val, val); |
| if (*error == true) |
| done = true; |
| break; |
| case '-': |
| s = ++nexts; |
| if (*nexts == ',' || *nexts == '\0') |
| val2 = last; |
| else |
| { |
| val2 = get_next_number (s, &nexts, error); |
| if (*error == true) |
| { |
| done = true; |
| break; |
| } |
| } |
| if (val > val2) |
| { |
| *error = true; |
| done = true; |
| break; |
| } |
| *error = include_range (val, val2); |
| if (*error == true) |
| { |
| done = true; |
| break; |
| } |
| if (*nexts == ',') |
| { |
| s = ++nexts; |
| break; |
| } |
| if (*nexts == '\0') |
| { |
| done = true; |
| break; |
| } |
| break; |
| case '\0': |
| *error = include_range (val, val); |
| done = true; |
| break; |
| default: |
| *error = true; |
| done = true; |
| break; |
| } |
| } |
| // if there was a parser error leave old setting |
| if (*error == true) |
| { |
| if (items) |
| { |
| items->destroy (); |
| delete items; |
| } |
| items = olditems; |
| return false; |
| } |
| } |
| |
| if (first != (uint64_t) - 1 && last != (uint64_t) - 1) |
| { |
| for (long i = VecSize (items) - 1; i >= 0; i--) |
| { |
| RangePair *rp = items->get (i); |
| if ((rp->first > last) || (rp->last < first)) |
| { |
| delete rp; |
| items->remove (i); |
| continue; |
| } |
| if (rp->first < first) |
| rp->first = first; |
| if (rp->last > last) |
| rp->last = last; |
| } |
| if (VecSize (items) == 1) |
| { |
| RangePair *rp = items->get (0); |
| if ((rp->first == first) && (rp->last == last)) |
| { |
| // All, leave items NULL |
| items->destroy (); |
| delete items; |
| items = NULL; |
| } |
| } |
| } |
| |
| // no error, delete the old setting |
| if (olditems != NULL) |
| { |
| olditems->destroy (); |
| delete olditems; |
| } |
| |
| bool changed; |
| // regenerate the pattern |
| if (pattern == NULL) |
| changed = true; |
| else |
| { |
| char *oldpattern = pattern; |
| pattern = NULL; // to force a recompute with new values |
| (void) get_pattern (); |
| changed = strcmp (pattern, oldpattern) != 0; |
| free (oldpattern); |
| } |
| return changed; |
| } |
| |
| //================================================================ |
| // Protected methods |
| |
| // set_status -- regenerate the status line, describing the current setting |
| void |
| FilterNumeric::update_status () |
| { |
| // regenerate the status line |
| free (status); |
| nselected = 0; |
| if (items == NULL) |
| { |
| if (last == (uint64_t) - 1 && last == first) |
| // neither set; data not available |
| status = dbe_sprintf (GTXT ("(data not recorded)")); |
| else if (first == (uint64_t) - 1 || last == (uint64_t) - 1) |
| // range was not set |
| status = dbe_sprintf (GTXT ("(all)")); |
| else |
| // range was set, compute percentage |
| status = dbe_sprintf (GTXT ("total %lld, range: %lld-%lld"), |
| (long long) nitems, (long long) first, |
| (long long) last); |
| } |
| else |
| { |
| // some are selected |
| int index; |
| RangePair *rp; |
| Vec_loop (RangePair *, items, index, rp) |
| { |
| nselected += rp->last - rp->first + 1; |
| } |
| if (last == (uint64_t) - 1) |
| // range was not set |
| status = dbe_sprintf (GTXT ("(%lld items selected)"), |
| (long long) nselected); |
| else |
| // range was set |
| status = dbe_sprintf (GTXT ("total %lld, range: %lld-%lld"), |
| (long long) nitems, (long long) first, |
| (long long) last); |
| } |
| } |
| |
| // Add a range to the filter; called from set_pattern for each index, |
| // or index pair |
| bool |
| FilterNumeric::include_range (uint64_t findex, uint64_t lindex) |
| { |
| int index; |
| RangePair *rp; |
| if (findex > lindex) |
| return true; |
| |
| bool done = false; |
| if (items == NULL) |
| items = new Vector<RangePair *>(0); |
| |
| Vec_loop (RangePair *, items, index, rp) |
| { |
| if (findex < rp->first) |
| { |
| // Case where the new pair starts before the old |
| if (lindex + 1 < rp->first) |
| { |
| // this pair comes cleanly in front of the current item |
| RangePair *rp2 = new RangePair (); |
| rp2->first = findex; |
| rp2->last = lindex; |
| items->insert (index, rp2); |
| done = true; |
| break; |
| } |
| // This new one extends the previous from the front |
| rp->first = findex; |
| chkextend: |
| if (lindex <= rp->last) |
| { |
| // but does not extend the back |
| done = true; |
| break; |
| } |
| // extend this one out |
| rp->last = lindex; |
| |
| // does it go into the next range? |
| if (index == items->size () - 1) |
| { |
| // this is the last range, so it does not |
| done = true; |
| break; |
| } |
| RangePair *next = items->fetch (index + 1); |
| if (lindex + 1 < next->first) |
| { |
| // no extension, we're done |
| done = true; |
| break; |
| } |
| // it does extend the next one |
| next->first = rp->first; |
| rp = next; |
| // remove the current one, promoting next |
| items->remove (index); |
| goto chkextend; |
| } |
| else if (findex > rp->last + 1) |
| // the new one is completely beyond the current |
| continue; |
| else |
| { |
| // the new one may start at or after the current, but it |
| // extends it out; set the current |
| // this pair overlaps the current item |
| // rp-> first is OK -- it's equal or less than findex |
| goto chkextend; |
| } |
| } |
| |
| if (done != true) |
| { |
| // fall through -- append to list |
| rp = new RangePair (); |
| rp->first = findex; |
| rp->last = lindex; |
| items->append (rp); |
| } |
| |
| return false; |
| } |
| |
| // Scan the filter to see if the number given is filtered in or out |
| // return true if number is in, false if it's out |
| bool |
| FilterNumeric::is_selected (uint64_t number) |
| { |
| int index; |
| RangePair *rp; |
| if (items == NULL) |
| return true; |
| if (items->size () == 0) |
| return false; |
| |
| Vec_loop (RangePair *, items, index, rp) |
| { |
| if (number >= rp->first && number <= rp->last) |
| return true; |
| } |
| return false; |
| } |
| |
| // get_next_number |
| // Called from parser to extract a number from the current string position |
| // Sets fail true if there was an error, false otherwise |
| // returns the number as parsed |
| uint64_t |
| FilterNumeric::get_next_number (char *s, char **e, bool *fail) |
| { |
| errno = 0; |
| *fail = false; |
| uint64_t val = strtoll (s, e, 10); |
| if (errno == EINVAL) |
| *fail = true; |
| return (val); |
| } |