You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Debug.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. /*
  2. * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.commandparser.commands.global;
  23. import com.dmdirc.FrameContainer;
  24. import com.dmdirc.Main;
  25. import com.dmdirc.Server;
  26. import com.dmdirc.commandparser.CommandArguments;
  27. import com.dmdirc.commandparser.CommandInfo;
  28. import com.dmdirc.commandparser.CommandManager;
  29. import com.dmdirc.commandparser.CommandType;
  30. import com.dmdirc.commandparser.commands.Command;
  31. import com.dmdirc.commandparser.commands.IntelligentCommand;
  32. import com.dmdirc.commandparser.commands.context.CommandContext;
  33. import com.dmdirc.config.ConfigManager;
  34. import com.dmdirc.config.Identity;
  35. import com.dmdirc.config.IdentityManager;
  36. import com.dmdirc.logger.ErrorLevel;
  37. import com.dmdirc.logger.Logger;
  38. import com.dmdirc.plugins.PluginManager;
  39. import com.dmdirc.plugins.Service;
  40. import com.dmdirc.plugins.ServiceProvider;
  41. import com.dmdirc.ui.input.AdditionalTabTargets;
  42. import com.dmdirc.ui.messages.Styliser;
  43. import com.dmdirc.updater.UpdateChecker;
  44. import java.io.Serializable;
  45. import java.util.Comparator;
  46. import java.util.Map;
  47. import java.util.SortedSet;
  48. import java.util.TreeSet;
  49. import java.util.Map.Entry;
  50. /**
  51. * Provides various handy ways to test or debug the client.
  52. *
  53. * @author Chris
  54. */
  55. public class Debug extends Command implements IntelligentCommand, CommandInfo {
  56. /**
  57. * Creates a new instance of Debug.
  58. */
  59. public Debug() {
  60. CommandManager.registerCommand(this);
  61. }
  62. /** {@inheritDoc} */
  63. @Override
  64. public void execute(final FrameContainer<?> origin,
  65. final CommandArguments args, final CommandContext context) {
  66. if (args.getArguments().length == 0) {
  67. showUsage(origin, args.isSilent(), "debug", "<debug command> [options]");
  68. } else if ("error".equals(args.getArguments()[0])) {
  69. doError(args.getArguments());
  70. } else if ("showraw".equals(args.getArguments()[0])) {
  71. doShowRaw(origin, args.isSilent());
  72. } else if ("configstats".equals(args.getArguments()[0])) {
  73. doConfigStats(origin, args.isSilent(), args.getArguments());
  74. } else if ("configinfo".equals(args.getArguments()[0])) {
  75. doConfigInfo(origin, args.isSilent());
  76. } else if ("globalconfiginfo".equals(args.getArguments()[0])) {
  77. doGlobalConfigInfo(origin, args.isSilent());
  78. } else if ("colourspam".equals(args.getArguments()[0])) {
  79. doColourSpam(origin, args.isSilent());
  80. } else if ("meminfo".equals(args.getArguments()[0])) {
  81. doMemInfo(origin, args.isSilent());
  82. } else if ("rungc".equals(args.getArguments()[0])) {
  83. doGarbage(origin, args.isSilent());
  84. } else if ("threads".equals(args.getArguments()[0])) {
  85. doThreads(origin, args.isSilent());
  86. } else if ("forceupdate".equals(args.getArguments()[0])) {
  87. doForceUpdate(origin, args.isSilent());
  88. } else if ("serverinfo".equals(args.getArguments()[0])) {
  89. doServerInfo(origin, args.isSilent());
  90. } else if ("serverstate".equals(args.getArguments()[0])) {
  91. doServerState(origin, args.isSilent());
  92. } else if ("benchmark".equals(args.getArguments()[0])) {
  93. doBenchmark(origin);
  94. } else if ("services".equals(args.getArguments()[0])) {
  95. doServices(origin, args.isSilent(), args.getArguments());
  96. } else if ("firstrun".equals(args.getArguments()[0])) {
  97. Main.getUI().showFirstRunWizard();
  98. } else if ("migration".equals(args.getArguments()[0])) {
  99. Main.getUI().showMigrationWizard();
  100. } else if ("notify".equals(args.getArguments()[0])) {
  101. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "Current notification colour is: "
  102. + origin.getNotification());
  103. } else {
  104. sendLine(origin, args.isSilent(), FORMAT_ERROR, "Unknown debug action.");
  105. }
  106. }
  107. /**
  108. * Generates a fake error.
  109. *
  110. * @param args The arguments that were passed to the command
  111. */
  112. private static void doError(final String ... args) {
  113. ErrorLevel el = ErrorLevel.HIGH;
  114. if (args.length > 2) {
  115. final String level = args[2];
  116. if (level.equals("low")) {
  117. el = ErrorLevel.LOW;
  118. } else if (level.equals("medium")) {
  119. el = ErrorLevel.MEDIUM;
  120. } else if (level.equals("fatal")) {
  121. el = ErrorLevel.FATAL;
  122. } else if (level.equals("unknown")) {
  123. el = ErrorLevel.UNKNOWN;
  124. }
  125. }
  126. if (args.length > 1 && args[1].equals("user")) {
  127. Logger.userError(el, "Debug error message");
  128. } else {
  129. Logger.appError(el, "Debug error message", new Exception());
  130. }
  131. }
  132. /**
  133. * Attempts to show the server's raw window.
  134. *
  135. * @param origin The window this command was executed in
  136. * @param isSilent Whether this command has been silenced or not
  137. */
  138. private void doShowRaw(final FrameContainer<?> origin, final boolean isSilent) {
  139. if (origin == null || origin.getServer() == null) {
  140. sendLine(origin, isSilent, FORMAT_ERROR, "Cannot show raw window here.");
  141. } else {
  142. origin.getServer().addRaw();
  143. }
  144. }
  145. /**
  146. * Shows stats related to the config system.
  147. *
  148. * @param origin The window this command was executed in
  149. * @param isSilent Whether this command has been silenced or not
  150. * @param args Arguments array for the command
  151. */
  152. private void doConfigStats(final FrameContainer<?> origin, final boolean isSilent,
  153. final String[] args) {
  154. int arg = -1;
  155. if (args.length == 2) {
  156. if (args[1].startsWith("+")) {
  157. try {
  158. arg = Integer.parseInt(args[1].substring(1));
  159. } catch (NumberFormatException e) {
  160. arg = Integer.MAX_VALUE;
  161. }
  162. doConfigStatsTop(origin, isSilent, arg);
  163. } else if (args[1].matches("^[0-9]+$")) {
  164. try {
  165. arg = Integer.parseInt(args[1]);
  166. } catch (NumberFormatException e) {
  167. arg = -1;
  168. }
  169. doConfigStatsCutOff(origin, isSilent, arg);
  170. } else {
  171. doConfigStatsOption(origin, isSilent, args[1]);
  172. }
  173. } else {
  174. doConfigStatsCutOff(origin, isSilent, arg);
  175. }
  176. }
  177. /**
  178. * Shows stats related to the config system, showing any options matching
  179. * the regex
  180. *
  181. * @param origin The window this command was executed in
  182. * @param isSilent Whether this command has been silenced or not
  183. * @param regex Regex to match options against
  184. */
  185. private void doConfigStatsOption(final FrameContainer<?> origin,
  186. final boolean isSilent, final String regex) {
  187. final SortedSet<Entry<String, Integer>> sortedStats = getSortedStats();
  188. boolean found = false;
  189. for (Map.Entry<String, Integer> entry : sortedStats) {
  190. if (entry.getKey().matches(regex)) {
  191. sendLine(origin, isSilent, FORMAT_OUTPUT, entry.getKey() + " - " +
  192. entry.getValue());
  193. found = true;
  194. }
  195. }
  196. if (!found) {
  197. sendLine(origin, isSilent, FORMAT_ERROR, "Unable to locate option.");
  198. }
  199. }
  200. /**
  201. * Shows stats related to the config system, listing the top X number of
  202. * options.
  203. *
  204. * @param origin The window this command was executed in
  205. * @param isSilent Whether this command has been silenced or not
  206. * @param top Top number of entries to show
  207. */
  208. private void doConfigStatsTop(final FrameContainer<?> origin,
  209. final boolean isSilent, final int top) {
  210. final SortedSet<Entry<String, Integer>> sortedStats = getSortedStats();
  211. int i = 0;
  212. for (Map.Entry<String, Integer> entry : sortedStats) {
  213. if (i == top) {
  214. break;
  215. }
  216. i++;
  217. sendLine(origin, isSilent, FORMAT_OUTPUT, entry.getKey() + " - " +
  218. entry.getValue());
  219. }
  220. }
  221. /**
  222. * Shows stats related to the config system, lists all values with number
  223. * of usages over the specified value
  224. *
  225. * @param origin The window this command was executed in
  226. * @param isSilent Whether this command has been silenced or not
  227. * @param cutoff Cut off value for stats
  228. */
  229. private void doConfigStatsCutOff(final FrameContainer<?> origin,
  230. final boolean isSilent, final int cutoff) {
  231. final SortedSet<Entry<String, Integer>> sortedStats = getSortedStats();
  232. for (Map.Entry<String, Integer> entry : sortedStats) {
  233. if (entry.getValue() <= cutoff) {
  234. break;
  235. }
  236. sendLine(origin, isSilent, FORMAT_OUTPUT, entry.getKey() + " - " +
  237. entry.getValue());
  238. }
  239. }
  240. /**
  241. * Gets a sorted Set of config options and the number of times they have
  242. * been called.
  243. *
  244. * @return Sorted set of config options and usages
  245. */
  246. private static SortedSet<Entry<String, Integer>> getSortedStats() {
  247. final SortedSet<Entry<String, Integer>> sortedStats =
  248. new TreeSet<Entry<String, Integer>>(new ValueComparator());
  249. sortedStats.addAll(ConfigManager.getStats().entrySet());
  250. return sortedStats;
  251. }
  252. /**
  253. * Shows memory usage information.
  254. *
  255. * @param origin The window this command was executed in
  256. * @param isSilent Whether this command has been silenced or not
  257. */
  258. private void doMemInfo(final FrameContainer<?> origin, final boolean isSilent) {
  259. sendLine(origin, isSilent, FORMAT_OUTPUT, "Total Memory: "
  260. + Runtime.getRuntime().totalMemory());
  261. sendLine(origin, isSilent, FORMAT_OUTPUT, "Free Memory: "
  262. + Runtime.getRuntime().freeMemory());
  263. sendLine(origin, isSilent, FORMAT_OUTPUT, "Used Memory: "
  264. + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
  265. }
  266. /**
  267. * Outputs 100 lines containing various colours.
  268. *
  269. * @param origin The window this command was executed in
  270. * @param isSilent Whether this command has been silenced or not
  271. */
  272. private void doColourSpam(final FrameContainer<?> origin, final boolean isSilent) {
  273. for (int i = 0; i < 100; i++) {
  274. sendLine(origin, isSilent, FORMAT_OUTPUT, ((char) 3) + "5Colour! "
  275. + ((char) 3) + "6Colour! " + ((char) 3) + "7Colour! "
  276. + ((char) 3) + "6Colour! " + ((char) 3) + "7Colour! "
  277. + ((char) 3) + "6Colour! " + ((char) 3) + "7Colour! "
  278. + ((char) 3) + "6Colour! " + ((char) 3) + "7Colour! ");
  279. }
  280. }
  281. /**
  282. * Manually runs the garbage collector.
  283. *
  284. * @param origin The window this command was executed in
  285. * @param isSilent Whether this command has been silenced or not
  286. */
  287. private void doGarbage(final FrameContainer<?> origin, final boolean isSilent) {
  288. System.gc();
  289. sendLine(origin, isSilent, FORMAT_OUTPUT, "Invoked garbage collector.");
  290. }
  291. /**
  292. * Shows information about the config manager.
  293. *
  294. * @param origin The window this command was executed in
  295. * @param isSilent Whether this command has been silenced or not
  296. */
  297. private void doConfigInfo(final FrameContainer<?> origin, final boolean isSilent) {
  298. for (Identity source : origin.getConfigManager().getSources()) {
  299. sendLine(origin, isSilent, FORMAT_OUTPUT, source.getTarget() + " - "
  300. + source + "(" + source.getTarget().getOrder() + ")");
  301. }
  302. }
  303. /**
  304. * Shows information about the global config manager.
  305. *
  306. * @param origin The window this command was executed in
  307. * @param isSilent Whether this command has been silenced or not
  308. */
  309. private void doGlobalConfigInfo(final FrameContainer<?> origin, final boolean isSilent) {
  310. for (Identity source : IdentityManager.getGlobalConfig().getSources()) {
  311. sendLine(origin, isSilent, FORMAT_OUTPUT, source.getTarget() + " - "
  312. + source + "(" + source.getTarget().getOrder() + ")");
  313. }
  314. }
  315. /**
  316. * Forces the update checker to check for updates.
  317. *
  318. * @param origin The window this command was executed in
  319. * @param isSilent Whether this command has been silenced or not
  320. */
  321. private void doForceUpdate(final FrameContainer<?> origin, final boolean isSilent) {
  322. if (IdentityManager.getGlobalConfig().getOptionBool("updater", "enable")) {
  323. new Thread(new UpdateChecker(), "Forced update checker").start();
  324. } else {
  325. sendLine(origin, isSilent, FORMAT_ERROR, "Update checking is currenty disabled."
  326. + " You can enable it by typing:");
  327. sendLine(origin, isSilent, FORMAT_ERROR, Styliser.CODE_FIXED
  328. + " /set updater enable true");
  329. }
  330. }
  331. /**
  332. * Shows information about active threads.
  333. *
  334. * @param origin The window this command was executed in
  335. * @param isSilent Whether this command has been silenced or not
  336. */
  337. private void doThreads(final FrameContainer<?> origin, final boolean isSilent) {
  338. for (Entry<Thread, StackTraceElement[]> thread: Thread.getAllStackTraces().entrySet()) {
  339. sendLine(origin, isSilent, FORMAT_OUTPUT, Styliser.CODE_BOLD
  340. + thread.getKey().getName());
  341. for (StackTraceElement element : thread.getValue()) {
  342. sendLine(origin, isSilent, FORMAT_OUTPUT, Styliser.CODE_FIXED
  343. + " " + element.toString());
  344. }
  345. }
  346. }
  347. /**
  348. * Shows information about the current server's state.
  349. *
  350. * @param origin The window this command was executed in
  351. * @param isSilent Whether this command has been silenced or not
  352. */
  353. private void doServerState(final FrameContainer<?> origin, final boolean isSilent) {
  354. if (origin.getServer() == null) {
  355. sendLine(origin, isSilent, FORMAT_ERROR, "This window isn't connected to a server");
  356. } else {
  357. final Server server = origin.getServer();
  358. sendLine(origin, isSilent, FORMAT_OUTPUT, server.getStatus().getTransitionHistory());
  359. }
  360. }
  361. /**
  362. * Shows information about the current server.
  363. *
  364. * @param origin The window this command was executed in
  365. * @param isSilent Whether this command has been silenced or not
  366. */
  367. private void doServerInfo(final FrameContainer<?> origin, final boolean isSilent) {
  368. if (origin.getServer() == null) {
  369. sendLine(origin, isSilent, FORMAT_ERROR, "This window isn't connected to a server");
  370. } else {
  371. final Server server = origin.getServer();
  372. sendLine(origin, isSilent, FORMAT_OUTPUT, "Server name: " + server.getName());
  373. sendLine(origin, isSilent, FORMAT_OUTPUT, "Actual name: "
  374. + server.getParser().getServerName());
  375. sendLine(origin, isSilent, FORMAT_OUTPUT, "Network: " + server.getNetwork());
  376. sendLine(origin, isSilent, FORMAT_OUTPUT, "IRCd: "
  377. + server.getParser().getServerSoftware() + " - "
  378. + server.getParser().getServerSoftwareType());
  379. sendLine(origin, isSilent, FORMAT_OUTPUT, "Modes: "
  380. + server.getParser().getBooleanChannelModes() + " "
  381. + server.getParser().getListChannelModes() + " "
  382. + server.getParser().getParameterChannelModes() + " "
  383. + server.getParser().getDoubleParameterChannelModes());
  384. }
  385. }
  386. /**
  387. * Benchmarks the textpane.
  388. *
  389. * @param origin The window this command was executed in
  390. */
  391. private static void doBenchmark(final FrameContainer<?> origin) {
  392. long[] results = new long[10];
  393. for (int i = 0; i < results.length; i++) {
  394. final long start = System.nanoTime();
  395. for (int j = 0; j < 5000; j++) {
  396. origin.addLine(FORMAT_OUTPUT, "This is a benchmark. Lorem ipsum doler...");
  397. }
  398. final long end = System.nanoTime();
  399. results[i] = end - start;
  400. }
  401. for (int i = 0; i < results.length; i++) {
  402. origin.addLine(FORMAT_OUTPUT, "Iteration " + i + ": " + results[i]
  403. + " nanoseconds.");
  404. }
  405. }
  406. /**
  407. * Shows information about all the current services available to plugins.
  408. *
  409. * @param origin The window this command was executed in
  410. * @param isSilent Whether this command has been silenced or not
  411. * @param args The arguments that were passed to the command
  412. */
  413. private void doServices(final FrameContainer<?> origin, final boolean isSilent,
  414. final String[] args) {
  415. sendLine(origin, isSilent, FORMAT_OUTPUT, "Available Services:");
  416. for (Service service : PluginManager.getPluginManager().getAllServices()) {
  417. sendLine(origin, isSilent, FORMAT_OUTPUT, " " + service.toString());
  418. if (args.length > 1 && args[1].equals("full")) {
  419. for (ServiceProvider provider : service.getProviders()) {
  420. sendLine(origin, isSilent, FORMAT_OUTPUT, " "
  421. + provider.getProviderName() + " [Active: "
  422. + provider.isActive() + "]");
  423. }
  424. }
  425. }
  426. }
  427. /** {@inheritDoc} */
  428. @Override
  429. public String getName() {
  430. return "debug";
  431. }
  432. /** {@inheritDoc} */
  433. @Override
  434. public boolean showInHelp() {
  435. return false;
  436. }
  437. /** {@inheritDoc} */
  438. @Override
  439. public CommandType getType() {
  440. return CommandType.TYPE_GLOBAL;
  441. }
  442. /** {@inheritDoc} */
  443. @Override
  444. public String getHelp() {
  445. return null;
  446. }
  447. /** {@inheritDoc} */
  448. @Override
  449. public AdditionalTabTargets getSuggestions(final int arg,
  450. final IntelligentCommandContext context) {
  451. final AdditionalTabTargets res = new AdditionalTabTargets();
  452. res.excludeAll();
  453. if (arg == 0) {
  454. res.add("error");
  455. res.add("showraw");
  456. res.add("colourspam");
  457. res.add("configstats");
  458. res.add("meminfo");
  459. res.add("rungc");
  460. res.add("configinfo");
  461. res.add("globalconfiginfo");
  462. res.add("forceupdate");
  463. res.add("serverinfo");
  464. res.add("serverstate");
  465. res.add("threads");
  466. res.add("benchmark");
  467. res.add("firstrun");
  468. res.add("migration");
  469. res.add("notify");
  470. res.add("services");
  471. } else if (arg == 1 && "error".equals(context.getPreviousArgs().get(0))) {
  472. res.add("user");
  473. res.add("app");
  474. } else if (arg == 1 && "services".equals(context.getPreviousArgs().get(0))) {
  475. res.add("full");
  476. } else if (arg == 2 && "error".equals(context.getPreviousArgs().get(0))) {
  477. res.add("low");
  478. res.add("medium");
  479. res.add("high");
  480. res.add("fatal");
  481. res.add("unknown");
  482. }
  483. return res;
  484. }
  485. /** Reverse value comparator for a map entry. */
  486. private static class ValueComparator implements
  487. Comparator<Entry<String, Integer>>, Serializable {
  488. /**
  489. * A version number for this class. It should be changed whenever the
  490. * class structure is changed (or anything else that would prevent
  491. * serialized objects being unserialized with the new class).
  492. */
  493. private static final long serialVersionUID = 1;
  494. /** {@inheritDoc} */
  495. @Override
  496. public int compare(final Entry<String, Integer> o1,
  497. final Entry<String, Integer> o2) {
  498. int returnValue = o1.getValue().compareTo(o2.getValue()) * -1;
  499. if (returnValue == 0) {
  500. returnValue = o1.getKey().compareToIgnoreCase(o2.getKey());
  501. }
  502. return returnValue;
  503. }
  504. }
  505. }