【问题标题】:Text Search based algorithm not behaving as intended基于文本搜索的算法未按预期运行
【发布时间】:2015-07-05 23:08:20
【问题描述】:

更新

我已经用其他 SO 用户建议的更新代码更新了这个问题,并将澄清以前存在的任何模棱两可的文本。

更新 #2

我只能访问相关应用程序生成的日志文件。因此,我被限制在日志文件的内容内工作,并且没有超出该范围的解决方案是完全可能的。我会稍微修改一下示例数据。我想指出以下关键变量

Thread ID - 范围从 0..19 - 一个线程被多次使用。因此ScriptExecThread(2) 可能会在日志中出现多次。

Script - 每个线程都会在特定文件上运行脚本。同样的脚本可能在同一个线程上运行,但不会在同一个线程和文件上运行。

File - 每个Thread IDFile 上运行一个Script。如果Thread(10)myfile.file 上运行myscript.script,则不会再次执行该EXACT 行。使用上述示例的成功示例将是这样的。

--------开始-----

Thread(10) 在 myfile.file 上启动 myscript.script

Thread(10) 在 myfile.file 上完成了 myscript.script

--------结束--------

使用上述示例的不成功示例是:

--------开始-----

Thread(10) 在 myfile.file 上启动 myscript.script

--------结束------


在解决我的查询之前,我将简要介绍所使用的代码和所需的行为。


总结

我目前正在解析大型日志文件(平均需要 100k - 600k 行),并尝试按特定顺序检索某些信息。我已经计算出我的请求背后的布尔代数,它似乎在纸上有效,但在代码上却没有那么多(我一定错过了一些明显的东西)。我想提前通知一下,代码没有经过任何优化或优化,现在我只是想让它工作。

在此日志文件中,您可以看到某些线程在启动但从未完成时挂起。可能的线程 ID 范围的数量。这是一些伪代码:

    REGEX = "ScriptExecThread(\\([0-9]+\\)).*?(finished|starting)" //in java
    Set started, finished
    for (int i=log.size()-1; i >=0; i--) {
    if(group(2).contains("starting")
        started.add(log.get(i))
    else if(group(2).contains("finished")
        finished.add(log.get(i)    
    }
    started.removeAll(finished);

搜索挂起的线程

Set<String> started = new HashSet<String>(), finished = new HashSet<String>();
            
for(int i = JAnalyzer.csvlog.size()-1; i >= 0; i--) {
    if(JAnalyzer.csvlog.get(i).contains("ScriptExecThread")) 
        JUtility.hasThreadHung(JAnalyzer.csvlog.get(i), started, finished);     
}
started.removeAll(finished);
            
commonTextArea.append("Number of threads hung: " + noThreadsHung + "\n");
for(String s : started) { 
    JLogger.appendLineToConsole(s);
    commonTextArea.append(s+"\n");
}

已挂起线程

public static boolean hasThreadHung(final String str, Set<String> started, Set<String> finished) {      
    Pattern r = Pattern.compile("ScriptExecThread(\\([0-9]+\\)).*?(finished|starting)");
    Matcher m = r.matcher(str);
    boolean hasHung = m.find();
    
        if(m.group(2).contains("starting"))
            started.add(str);
        else if (m.group(2).contains("finished"))
            finished.add(str);
        
        System.out.println("Started size: " + started.size());
        System.out.println("Finished size: " + finished.size());
        
    return hasHung;
}

示例数据

ScriptExecThread(1) 在 afile.xyz 上启动

ScriptExecThread(2) 在 bfile.abc 上启动

ScriptExecThread(3) 在 cfile.zyx 上启动

ScriptExecThread(4) 在 dfile.zxy 上启动

ScriptExecThread(5) 在 efile.yzx 上启动

ScriptExecThread(1) 在 afile.xyz 上完成

ScriptExecThread(2) 在 bfile.abc 上完成

ScriptExecThread(3) 在 cfile.zyx 上完成

ScriptExecThread(4) 在 dfile.zxy 上完成

ScriptExecThread(5) 在 efile.yzy 上完成

ScriptExecThread(1) 在 bfile.abc 上启动

ScriptExecThread(2) 在 dfile.zxy 上启动

ScriptExecThread(3) 在 afile.xyz 上启动

ScriptExecThread(1) 在 bfile.abc 上完成

日志结束

如果你举个例子,你会注意到 2 号和 3 号线程已启动但未能完成(原因不是必需的,我只需要获取线路)。

样本数据

09.08 15:06.53, ScriptExecThread(7),Info,########### 开始

09.08 15:06.54, ScriptExecThread(18),Info,###################### 开始

09.08 15:06.54, ScriptExecThread(13),Info,######## 完成于#########

09.08 15:06.54, ScriptExecThread(13),Info,########## 开始

09.08 15:06.55, ScriptExecThread(9),Info,##### ########完成

09.08 15:06.55, ScriptExecThread(0),Info,####finished in ###########

09.08 15:06.55, ScriptExecThread(19),Info,#### 完成于########

09.08 15:06.55, ScriptExecThread(8),Info,###### 完成于 2777 #########

09.08 15:06.55, ScriptExecThread(19),Info,########## 开始

09.08 15:06.55, ScriptExecThread(8),Info,####### 开始

09.08 15:06.55,ScriptExecThread(0),信息,##########starting

09.08 15:06.55, ScriptExecThread(19),Info,Post ###### 完成于 #####

09.08 15:06.55, ScriptExecThread(0),Info,###### 完成于#########

09.08 15:06.55, ScriptExecThread(19),Info,########## 开始

09.08 15:06.55, ScriptExecThread(0),Info,########### 开始

09.08 15:06.55, ScriptExecThread(9),Info,########## 开始

09.08 15:06.56,ScriptExecThread(1),Info,####### ######## 完成

09.08 15:06.56, ScriptExecThread(17),Info,###### 完成于#######

09.08 15:06.56,ScriptExecThread(17),信息,##################### 开始

09.08 15:06.56, ScriptExecThread(1),Info,########## 开始


目前,代码仅显示整个日志文件,其中行以“starting”开头。当我查看代码时,这有点道理。

我已删除任何我不想显示的多余信息。如果有什么我可能遗漏的,请随时告诉我,我会补充。

【问题讨论】:

  • 您的样本中似乎没有 ID 为 3 的线程;您是如何生成该输出的?。
  • @tucuxi - 我提供的数据只是为了让您了解部分日志的外观。实际数据超过 600,000 行
  • 此行的目的是什么:else if (hasFinished &amp;&amp; hasStarted)?一行日志是否可以同时包含“开始”和“完成”消息?
  • @SashaSalauyou - 不完全是,一行可能都不包含(很少但可能)
  • @Juxhin 再次:这条语句else if (hasFinished &amp;&amp; hasStarted) 根据执行逻辑检查j 行是否包含线程iboth 消息。

标签: java algorithm text


【解决方案1】:

removeAll 永远不会工作。
hasThreadHung 正在存储整个字符串。
所以started 中的值永远不会与finished 中的值匹配。

你想做这样的事情:

class ARecord {
    // Proper encapsulation of the members omitted for brevity
    String thread;
    String line;
    public ARecord (String thread, String line) {
        this.thread = thread;
        this.line = line;
    }
    public int hashcode() {
        return thread.hashcode();
    }
    public boolean equals(ARecord o) {
        return thread.equals(o.thread);
    }
}

然后在hasHungThread 中,创建一个ARecord 并将其添加到Sets。
例如:

started.add(new ARecord(m.group(2), str));

searchHungThreads 中,您将从started 中检索ARecord 并将其输出为:

for(ARecord rec : started) { 
    JLogger.appendLineToConsole(rec.line);
    commonTextArea.append(rec.line+"\n");
}

【讨论】:

  • 是的,回顾我发布的代码并没有多大意义,因为我现在想想。我很快就会尝试你和 Jay Kominek 的代码,并让你更新
【解决方案2】:

保留两组独立的startedfinished 线程(如@tucuxi 所述)是行不通的。如果 ID 为 5 的线程启动、运行和结束,那么 5 将永远出现在 finished 集中。如果另一个 ID 为 5 的线程启动并挂起,则不会报告。

不过,假设暂时没有重复使用线程 ID。曾经创建的每个线程都会收到一个新 ID。通过保持单独的 startedfinished 集,到您完成时,您将拥有数十万个元素。这些数据结构的性能与它们在操作时获得的数据成正比。性能不太可能对您的用例很重要,但如果您执行更昂贵的操作,或者在大 100 倍的数据上运行,它可能会。

前言不碍事,这是@tucuxi 代码的工作版本:

import java.util.*;
import java.io.*;
import java.util.regex.*;

public class T {
    public static Collection<String> findHung(Iterable<String> data) {
        Pattern p = Pattern.compile(
            "ScriptExecThread\\(([0-9]+).*?(finished|starting)");
        HashMap<Integer, String> running = new HashMap<Integer, String>();
        for (String d : data) {
            Matcher m = p.matcher(d);
            if (m.find()) {
                int n = Integer.parseInt(m.group(1));
                if (m.group(2).equals("starting"))
                    running.put(n, d);
                else
                    running.remove(n);
            }
        }
        return running.values();
    }

    static Iterable<String> readFile(String path, String encoding) throws IOException {
        final Scanner sc = new Scanner(new File(path), encoding).useDelimiter("\n");
        return new Iterable<String>() {
            public Iterator<String> iterator() { return sc; }
        };
    }

    public static void main(String[] args) throws Exception {
        for (String fileName : args) {
            for (String s : findHung(readFile(fileName, "UTF-8"))) {
                System.out.println(fileName + ": '" + s + "' unfinished");
            }
        }
    }
}

请注意,我已删除 finished 集,而 HashMap 现在称为 running。当新线程开始时,它们会进入,当线程完成时,它会被拉出。这意味着HashMap 将始终是当前正在运行的线程数的大小,它将始终小于(或等于)曾经运行的线程总数。所以对它的操作会更快。 (作为一个令人愉快的副作用,您现在可以逐个记录日志行上正在运行的线程数,这可能有助于确定线程挂起的原因。)

这是我用来生成大量测试用例的 Python 程序:

#!/usr/bin/python

from random import random, choice
from datetime import datetime
import tempfile

all_threads = set([])
running = []
hung = []
filenames = { }

target_thread_count = 16
hang_chance = 0.001

def log(id, msg):
    now = datetime.now().strftime("%m:%d %H:%M:%S")
    print "%s, ScriptExecThread(%i),Info,%s" % (now, id, msg)

def new_thread():
    if len(all_threads)>0:
        for t in range(0, 2+max(all_threads)):
            if t not in all_threads:
                all_threads.add(t)
                return t
    else:
        all_threads.add(0)
        return 0

for i in range(0, 100000):
    if len(running) > target_thread_count:
        new_thread_chance = 0.25
    else:
        new_thread_chance = 0.75
        pass

    if random() < new_thread_chance:
        t = new_thread()
        name = next(tempfile._get_candidate_names())+".txt"
        filenames[t] = name
        log(t, "%s starting" % (name,))
        if random() < hang_chance:
            hung.append(t)
        else:
            running.append(t)
    elif len(running)>0:
        victim = choice(running)
        all_threads.remove(victim)
        running.remove(victim)
        log(t, "%s finished" % (filenames[victim],))

【讨论】:

  • 非常有希望,谢谢。将对此进行测试,并在周三为您提供最新信息。
  • 我想到了同样的优化(只保留一组正在运行的线程),但是在 OP 的示例中,“完成,开始”的情况被标记为“未挂起”,尽管这段代码会标记他们作为“挂”。更复杂的版本会保留一个窗口,因此“完成,开始”只有在一定的时间间隔内才会被解释为“未挂起”;并且会发出许多其他日志文件不一致的信号。
  • 这不是优化;它是您正确实现它的方式:虽然问题提供的代码可能将“完成,开始”描述为“未挂起”,但@Juxhin 指出当前代码不起作用。在标记为“示例数据”的部分中,“开始、完成、开始”的情况被描述为“未能完成”。最后,生成日志文件的软件必须很奇怪,“开始、完成、开始”才能算作完成。碰巧它会更快是一个非常令人愉快的,并且(正如我指出的)在很大程度上无关紧要的副作用。
  • @JayKominek - 该软件显然最好避免任何挂起的线程。线程挂起的唯一时间是应用程序意外崩溃时,因此我正在编写这个分析工具,以便确定在应用程序崩溃之前我们哪些线程未能完成,以便我可以进一步调试。
  • @JayKominek - 似乎工作得很好,将对其进行正确测试并验证。谢谢:-)
【解决方案3】:

为什么不以另一种方式解决问题。如果你想要的只是挂起线程,可以以编程方式获取线程堆栈。也可以使用外部工具,但我认为自己的 JVM 内部是最简单的。然后将其公开为 API 或使用线程转储定期保存带有日期时间戳的文件。另一个程序只需要分析线程转储。如果同一个线程在同一个位置(相同的堆栈跟踪或相同的 3-5 个函数)超过线程转储,它很有可能挂起。

有一些工具可以帮助你分析https://www.google.com/search?q=java+thread+dump+tool+open+source

【讨论】:

  • 感谢您的输入,因为它确实是解决问题的直观方式(也很有趣!)。但是,日志是在我将获得的多台其他计算机上生成的。我无权访问应用程序本身,因此无法修改它以自动检测您提到的挂起线程。我只是解析 .csv 日志文件并尝试检测确定线程是否已挂起的模式,这将表明应用程序可能崩溃的原因(想象一个线程在单个文件上运行单个脚本 48 多个小时!)
  • 也有一些工具可以获取堆栈跟踪。在 linix 上只需 kill -9 或 jstack 在任何 JRE stackoverflow.com/questions/4876274/…
  • 看看我刚刚添加的更新。我不希望这样做过度。这只是我正在编写的当前分析应用程序的一个附加组件,目的是在应用程序运行时检测挂起的线程。
【解决方案4】:

如果我理解正确,您的文件很大,并且正在尝试为 X 的所有数值查找“X 开始(但未提及 X 已完成)”形式的模式。

如果我要这样做,我会使用这个伪代码:

Pattern p = Pattern.compile(
   "ScriptExecThread\\(([0-9]+).*?(finished|started)");
Set<Integer> started, finished;
Search for p; for each match m,
     int n = Integer.parseInt(m.group(1));
     if (m.group(2).equals("started")) started.add(n);
     else finished.add(n);
started.removeAll(finished); // found 'em: contains started-but-not-finished

这需要对每个文件进行一次正则表达式传递,并进行 O(size-of-finished) 集减法;它应该比您当前的方法快 20 倍。正则表达式将使用可选的 (|) 匹配来同时查找两个备选方案,从而减少通过次数。

编辑:明确正则表达式。编译一次正则表达式而不是每行一次应该会减少一些额外的运行时间。


编辑 2:实现的伪代码,对我有用


编辑 3:替换实现以显示文件和行。减少内存需求(不将整个文件加载到内存中);但打印该行确实需要存储所有“开始”行。

public class T {

    public static Collection<String> findHung(Iterable<String> data) {
        Pattern p = Pattern.compile(   
            "ScriptExecThread\\(([0-9]+).*?(finished|starting)");
        HashMap<Integer, String> started = new HashMap<Integer, String>();
        Set<Integer> finished = new HashSet<Integer>();
        for (String d : data) {
            Matcher m = p.matcher(d);
            if (m.find()) {
                int n = Integer.parseInt(m.group(1));
                if (m.group(2).equals("starting")) started.put(n, d);
                else finished.add(n);
            }                
        }
        for (int f : finished) {
            started.remove(f);
        }
        return started.values();
    }

    static Iterable<String> readFile(String path, String encoding) throws IOException {
        final Scanner sc = new Scanner(new File(path), encoding).useDelimiter("\n");
        return new Iterable<String>() {
            public Iterator<String> iterator() { return sc; }
        };
    }

    public static void main(String[] args) throws Exception {
        for (String fileName : args) {
            for (String s : findHung(readFile(fileName, "UTF-8"))) {
                System.out.println(fileName + ": '" + s + "' unfinished");
            }
        }
    }
}

输入:上面的示例数据,作为第一个参数,称为“data.txt”。另一个名为“data2.txt”的文件中的相同数据作为第二个参数 (javac T.java &amp;&amp; java T data.txt data2.txt)。输出:

data.txt: '    09.08 15:06.54, ScriptExecThread(18),Info,###################### starting' unfinished
data.txt: '    09.08 15:06.53, ScriptExecThread(7),Info,########### starting' unfinished
data2.txt: '    09.08 15:06.54, ScriptExecThread(18),Info,###################### starting' unfinished
data2.txt: '    09.08 15:06.53, ScriptExecThread(7),Info,########### starting' unfinished

【讨论】:

  • 另一个用户确实建议了相同的方法(使用 Set)。请问我可以要求更详细的答案吗? (伪有道理)
  • 那会更好,很好@tucuxi
  • @tucuxi - 你能再看看更新后的问题吗?
  • 在实现伪代码时,您似乎忽略了循环,并添加了字符串而不是匹配的数字。此外,您返回一个布尔值,而不是一组标识哪些线程已启动但未完成的数字。
  • 感谢@tucuxi 抽出宝贵时间。我会尽快尝试并及时通知您。
猜你喜欢
  • 2018-10-04
  • 1970-01-01
  • 2021-08-02
  • 1970-01-01
  • 2020-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多