Path: blob/master/src/hotspot/share/logging/logSelection.cpp
40930 views
/*1* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*22*/23#include "precompiled.hpp"24#include "jvm_io.h"25#include "utilities/ostream.hpp"26#include "logging/log.hpp"27#include "logging/logSelection.hpp"28#include "logging/logTagSet.hpp"29#include "runtime/os.hpp"30#include "utilities/globalDefinitions.hpp"31#include "utilities/ostream.hpp"32#include "utilities/quickSort.hpp"3334const LogSelection LogSelection::Invalid;3536LogSelection::LogSelection() : _ntags(0), _wildcard(false), _level(LogLevel::Invalid), _tag_sets_selected(0) {37}3839LogSelection::LogSelection(const LogTagType tags[LogTag::MaxTags], bool wildcard, LogLevelType level)40: _ntags(0), _wildcard(wildcard), _level(level), _tag_sets_selected(0) {41while (_ntags < LogTag::MaxTags && tags[_ntags] != LogTag::__NO_TAG) {42_tags[_ntags] = tags[_ntags];43_ntags++;44}4546for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) {47if (selects(*ts)) {48_tag_sets_selected++;49}50}51}5253bool LogSelection::operator==(const LogSelection& ref) const {54if (_ntags != ref._ntags ||55_wildcard != ref._wildcard ||56_level != ref._level ||57_tag_sets_selected != ref._tag_sets_selected) {58return false;59}60for (size_t i = 0; i < _ntags; i++) {61if (_tags[i] != ref._tags[i]) {62return false;63}64}65return true;66}6768bool LogSelection::operator!=(const LogSelection& ref) const {69return !operator==(ref);70}7172static LogSelection parse_internal(char *str, outputStream* errstream) {73// Parse the level, if specified74LogLevelType level = LogLevel::Unspecified;75char* equals = strchr(str, '=');76if (equals != NULL) {77const char* levelstr = equals + 1;78level = LogLevel::from_string(levelstr);79if (level == LogLevel::Invalid) {80if (errstream != NULL) {81errstream->print("Invalid level '%s' in log selection.", levelstr);82LogLevelType match = LogLevel::fuzzy_match(levelstr);83if (match != LogLevel::Invalid) {84errstream->print(" Did you mean '%s'?", LogLevel::name(match));85}86errstream->cr();87}88return LogSelection::Invalid;89}90*equals = '\0';91}9293size_t ntags = 0;94LogTagType tags[LogTag::MaxTags] = { LogTag::__NO_TAG };9596// Parse special tags such as 'all'97if (strcmp(str, "all") == 0) {98return LogSelection(tags, true, level);99}100101// Check for '*' suffix102bool wildcard = false;103char* asterisk_pos = strchr(str, '*');104if (asterisk_pos != NULL && asterisk_pos[1] == '\0') {105wildcard = true;106*asterisk_pos = '\0';107}108109// Parse the tag expression (t1+t2+...+tn)110char* plus_pos;111char* cur_tag = str;112do {113plus_pos = strchr(cur_tag, '+');114if (plus_pos != NULL) {115*plus_pos = '\0';116}117LogTagType tag = LogTag::from_string(cur_tag);118if (tag == LogTag::__NO_TAG) {119if (errstream != NULL) {120errstream->print("Invalid tag '%s' in log selection.", cur_tag);121LogTagType match = LogTag::fuzzy_match(cur_tag);122if (match != LogTag::__NO_TAG) {123errstream->print(" Did you mean '%s'?", LogTag::name(match));124}125errstream->cr();126}127return LogSelection::Invalid;128}129if (ntags == LogTag::MaxTags) {130if (errstream != NULL) {131errstream->print_cr("Too many tags in log selection '%s' (can only have up to " SIZE_FORMAT " tags).",132str, LogTag::MaxTags);133}134return LogSelection::Invalid;135}136tags[ntags++] = tag;137cur_tag = plus_pos + 1;138} while (plus_pos != NULL);139140for (size_t i = 0; i < ntags; i++) {141for (size_t j = 0; j < ntags; j++) {142if (i != j && tags[i] == tags[j]) {143if (errstream != NULL) {144errstream->print_cr("Log selection contains duplicates of tag %s.", LogTag::name(tags[i]));145}146return LogSelection::Invalid;147}148}149}150151return LogSelection(tags, wildcard, level);152}153154LogSelection LogSelection::parse(const char* str, outputStream* error_stream) {155char* copy = os::strdup_check_oom(str, mtLogging);156LogSelection s = parse_internal(copy, error_stream);157os::free(copy);158return s;159}160161bool LogSelection::selects(const LogTagSet& ts) const {162if (!_wildcard && _ntags != ts.ntags()) {163return false;164}165for (size_t i = 0; i < _ntags; i++) {166if (!ts.contains(_tags[i])) {167return false;168}169}170return true;171}172173static bool contains(LogTagType tag, const LogTagType tags[LogTag::MaxTags], size_t ntags) {174for (size_t i = 0; i < ntags; i++) {175if (tags[i] == tag) {176return true;177}178}179return false;180}181182bool LogSelection::consists_of(const LogTagType tags[LogTag::MaxTags]) const {183size_t i;184for (i = 0; tags[i] != LogTag::__NO_TAG; i++) {185if (!contains(tags[i], _tags, _ntags)) {186return false;187}188}189return i == _ntags;190}191192size_t LogSelection::ntags() const {193return _ntags;194}195196LogLevelType LogSelection::level() const {197return _level;198}199200size_t LogSelection::tag_sets_selected() const {201return _tag_sets_selected;202}203204int LogSelection::describe_tags(char* buf, size_t bufsize) const {205int tot_written = 0;206for (size_t i = 0; i < _ntags; i++) {207int written = jio_snprintf(buf + tot_written, bufsize - tot_written,208"%s%s", (i == 0 ? "" : "+"), LogTag::name(_tags[i]));209if (written == -1) {210return written;211}212tot_written += written;213}214215if (_wildcard) {216int written = jio_snprintf(buf + tot_written, bufsize - tot_written, "*");217if (written == -1) {218return written;219}220tot_written += written;221}222return tot_written;223}224225int LogSelection::describe(char* buf, size_t bufsize) const {226int tot_written = describe_tags(buf, bufsize);227228int written = jio_snprintf(buf + tot_written, bufsize - tot_written, "=%s", LogLevel::name(_level));229if (written == -1) {230return -1;231}232tot_written += written;233return tot_written;234}235236double LogSelection::similarity(const LogSelection& other) const {237// Compute Soerensen-Dice coefficient as the similarity measure238size_t intersecting = 0;239for (size_t i = 0; i < _ntags; i++) {240for (size_t j = 0; j < other._ntags; j++) {241if (_tags[i] == other._tags[j]) {242intersecting++;243break;244}245}246}247return 2.0 * intersecting / (_ntags + other._ntags);248}249250// Comparator used for sorting LogSelections based on their similarity to a specific LogSelection.251// A negative return value means that 'a' is more similar to 'ref' than 'b' is, while a positive252// return value means that 'b' is more similar.253// For the sake of giving short and effective suggestions, when two selections have an equal254// similarity score, the selection with the fewer tags (selecting the most tag sets) is considered255// more similar.256class SimilarityComparator {257const LogSelection& _ref;258public:259SimilarityComparator(const LogSelection& ref) : _ref(ref) {260}261int operator()(const LogSelection& a, const LogSelection& b) const {262const double epsilon = 1.0e-6;263264// Sort by similarity (descending)265double s = _ref.similarity(b) - _ref.similarity(a);266if (fabs(s) > epsilon) {267return s < 0 ? -1 : 1;268}269270// Then by number of tags (ascending)271int t = static_cast<int>(a.ntags() - (int)b.ntags());272if (t != 0) {273return t;274}275276// Lastly by tag sets selected (descending)277return static_cast<int>(b.tag_sets_selected() - a.tag_sets_selected());278}279};280281static const size_t suggestion_cap = 5;282static const double similarity_requirement = 0.3;283void LogSelection::suggest_similar_matching(outputStream* out) const {284LogSelection suggestions[suggestion_cap];285uint nsuggestions = 0;286287// See if simply adding a wildcard would make the selection match288if (!_wildcard) {289LogSelection sel(_tags, true, _level);290if (sel.tag_sets_selected() > 0) {291suggestions[nsuggestions++] = sel;292}293}294295// Check for matching tag sets with a single tag mismatching (a tag too many or short a tag)296for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) {297LogTagType tags[LogTag::MaxTags] = { LogTag::__NO_TAG };298for (size_t i = 0; i < ts->ntags(); i++) {299tags[i] = ts->tag(i);300}301302// Suggest wildcard selection unless the wildcard doesn't match anything extra303LogSelection sel(tags, true, _level);304if (sel.tag_sets_selected() == 1) {305sel = LogSelection(tags, false, _level);306}307308double score = similarity(sel);309310// Ignore suggestions with too low similarity311if (score < similarity_requirement) {312continue;313}314315// Cap not reached, simply add the new suggestion and continue searching316if (nsuggestions < suggestion_cap) {317suggestions[nsuggestions++] = sel;318continue;319}320321// Find the least matching suggestion already found, and if the new suggestion is a better match, replace it322double min = 1.0;323size_t pos = -1;324for (size_t i = 0; i < nsuggestions; i++) {325double score = similarity(suggestions[i]);326if (score < min) {327min = score;328pos = i;329}330}331if (score > min) {332suggestions[pos] = sel;333}334}335336if (nsuggestions == 0) {337// Found no similar enough selections to suggest.338return;339}340341// Sort found suggestions to suggest the best one first342SimilarityComparator sc(*this);343QuickSort::sort(suggestions, nsuggestions, sc, false);344345out->print("Did you mean any of the following?");346for (size_t i = 0; i < nsuggestions; i++) {347char buf[128];348suggestions[i].describe_tags(buf, sizeof(buf));349out->print(" %s", buf);350}351}352353354