newspaint

Documenting Problems That Were Difficult To Find The Answer To

Monthly Archives: January 2019

Adding More Details to Peers in Deluge Web Client

The “peers” tab for a given selected torrent in Deluge is rather scarce of information. By default it only displays:

  • Country
  • Address
  • Client
  • Progress
  • Download Speed
  • Upload Speed

I want more information – especially the flags (as uTorrent displays, or as close to).

So here are instructions on what to modify in order to get more information about peers displayed.

Firstly you may want to pin the deluge packages on your Linux/Ubuntu host to prevent them from being upgraded in the future (and wipe out your modifications):

me@host:~$ sudo apt-mark hold deluged
me@host:~$ sudo apt-mark hold deluge-web
me@host:~$ sudo apt-mark hold deluge-common

Edit the file torrent.py so that it returns more fields in the AJAX response to the query from the browser for peer detail to modify the get_peers() function to add the following lines:

            peerflags = str()
            if peer.flags & peer.snubbed:
                peerflags += "S"
            if peer.flags & peer.interesting:
                if not peer.flags & peer.remote_choked:
                    peerflags += "D"
                else:
                    peerflags += "d"
            elif not peer.flags & peer.remote_choked:
                peerflags += "K"
            if peer.flags & peer.remote_interested:
                if not peer.flags & peer.choked:
                    peerflags += "U"
                else:
                    peerflags += "u"
            elif not peer.flags & peer.choked:
                peerflags += "?"
            if peer.flags & peer.rc4_encrypted:
                peerflags += "E"
            if peer.flags & peer.plaintext_encrypted:
                peerflags += "e"
            if peer.flags & peer.optimistic_unchoke:
                peerflags += "O"
            if not peer.flags & peer.local_connection:
                peerflags += "I"
            if peer.flags & peer.endgame_mode:
                peerflags += "g"
            if peer.flags & peer.holepunched:
                peerflags += "h"
            #if peer.flags & peer.utp_socket:
            #    peerflags += "P"
            if peer.num_hashfails > 0:
                peerflags += "F"
            if peer.source & peer.dht:
                peerflags += "H"
            if peer.source & peer.pex:
                peerflags += "X"
            if peer.source & peer.lsd:
                peerflags += "L"
            if peer.source & peer.tracker:
                peerflags += "T"

            ret.append({
                "client": client,
                "country": country,
                "down_speed": peer.payload_down_speed,
                "ip": "%s:%s" % (peer.ip[0], peer.ip[1]),
                "progress": peer.progress,
                "seed": peer.flags & peer.seed,
                "up_speed": peer.payload_up_speed,
                "flags": peerflags,
                "total_download": peer.total_download,
                "total_upload": peer.total_upload,
                "rtt": peer.rtt,
            })

Next edit line 224 of the JavaScript file deluge-all.js to add the highlighted lines (but you may want to keep it all squashed up in one line in the file, it doesn’t matter):

...
function c(e){...}
function ms(e){return String.format('{0}ms',e);}
Deluge.details.PeersTab=...
...
{header:"Up Speed",width:100,sortable:true,renderer:fspeed,dataIndex:"up_speed"},
{header:"Flags",width:70,sortable:true,renderer:fplain,dataIndex:"flags"},
{header:"Downloaded",width:100,sortable:true,renderer:fsize,dataIndex:"total_download"},
{header:"Uploaded",width:100,sortable:true,renderer:fsize,dataIndex:"total_upload"},
{header:"RTT",width:70,sortable:true,renderer:ms,dataIndex:"rtt"}
],

So what do the flags mean?

  • S – peer snubbed
  • D – peer has what we want and remote not choked (usually downloading)
  • d – peer has what we want but remote is choked (want to download but they won’t)
  • K – peer doesn’t have what we want but isn’t choking us
  • U – peer wants what we have and we are not choked (usually uploading)
  • u – peer wants what we have and we are choked (peer wants us to upload but we won’t)
  • ? – peer doesn’t want what we have but we’re not choking them
  • E – RC4 (connection) encrypted
  • e – plaintext (handshake) encrypted
  • O – optimistic unchoke
  • I – incoming connection (peer initiated connection to us)
  • g – endgame mode
  • h – peer holepunched
  • P – uTP socket (flag not present in older versions of libtorrent)
  • F – peer has participated in a piece that failed hash
  • H – peer found via DHT
  • X – peer found via PEX
  • L – peer found via local service discovery (on local network)
  • T – peer found via tracker

See the peer_info structure in libtorrent for more detail.

A Utility to Convert Date Formats

The following Perl script converts from various forms to various forms:

#!/usr/bin/perl -w

use Time::Local;
use Date::Parse;

use strict;

sub help {
  print( STDERR "parsedate.pl - Utility for converting date formats\n" );
  print( STDERR "\n" );
  print( STDERR "Enter a date in one of the following formats:\n" );
  print( STDERR "  epoch: 1526615052 - time in seconds past 1 Jan 1970 UTC\n" );
  print( STDERR "  javaepoch: 1526615092916 - time in ms past 1 Jan 1970 UTC\n" );
  print( STDERR "\n" );
}

sub display_time {
  my ( $epoch ) = @_;

  printf( "\n" );

  my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);

  my @ltime = localtime( $epoch );
  my @gtime = gmtime( $epoch );

  my $min_diff = ( $ltime[2] - $gtime[2] ) * 60;
  $min_diff += ( $ltime[1] - $gtime[1] );
  if ( $ltime[3] != $gtime[3] ) {
    if ( $ltime[3] == ( $gtime[3] + 1 ) ) {
      $min_diff += ( 24 * 60 );
    } elsif ( $gtime[3] == ( $ltime[3] + 1 ) ) {
      $min_diff -= ( 24 * 60 );
    } elsif ( $ltime[3] == 1 ) {
      $min_diff += ( 24 * 60 );
    } else {
      $min_diff -= ( 24 * 60 );
    }
  }

  my $sign = "+";
  if ( $min_diff <= 0 ) {
    $min_diff = ( 0 - $min_diff );
    $sign = "-";
  }

  my $tz_hour = int( $min_diff / 60 );
  my $tz_min = $min_diff - ( $tz_hour * 60 );

  ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = @ltime;

  my $iso8601;
  $iso8601 = sprintf(
    "%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d",
    $year + 1900, $mon + 1, $mday,
    $hour, $min, $sec, $sign, $tz_hour, $tz_min
  );

  printf( "Local time:\n" );
  printf( "  %s\n", $iso8601 );
  printf( "  %s\n", scalar( localtime( $epoch ) ) );
  printf( "\n" );

  ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = @gtime;
  $iso8601 = sprintf(
    "%04d-%02d-%02dT%02d:%02d:%02dZ",
    $year + 1900, $mon + 1, $mday,
    $hour, $min, $sec
  );

  printf( "GMT/UTC:\n" );
  printf( "  %s\n", $iso8601 );
  printf( "  %s\n", scalar( gmtime( $epoch ) ) );
  printf( "  %d\n", $epoch );
  printf( "\n" );
}

sub main {
  # Join command line arguments into a single string
  my $input = join( " ", @ARGV );

  my $epoch = undef;

  if ( $input =~ m/^\s*$/ ) {
    help();

    print( "Using current time as no time string provided.\n\n" );
    $epoch = time();
  } elsif ( $input =~ m/^\d{1,10}$/ ) {
    printf( STDERR "Detected Unix epoch date format\n" );
    $epoch = int( $input );
  } elsif ( $input =~ m/^\d{13}$/ ) {
    printf( STDERR "Detected Java epoch (in ms) date format\n" );
    $epoch = ( int( $input ) / 1000.00 );
  } else {
    printf( STDERR "Trying to parse date string...\n" );
    $epoch = Date::Parse::str2time( $input );
  }

  if ( ! $epoch ) {
    die( "Failed to parse date" );
  }

  display_time( $epoch );
}

main();

Jstack Unable to Contact Target Process

In Java one of the most useful tools for diagnosing thread-related issues is jstack. It outputs a stack trace of every thread of a running process – and can also give an indication of the state of locks.

However I was getting the following error message when trying to attach jstack to a running process:

17840: Unable to open socket file: target process not responding or HotSpot VM not loaded
The -F option can be used when the target process is not responding

The solution, amazingly, was to run the jstack command as the same user as the running Java process.

The command to use for this is:

sudo -u <user> jstack -l <pid>

e.g. if the Java process is running as user “oracle” with process ID 45132 then you would execute:

sudo -u oracle jstack -l 45132

A Microsoft Word Macro to Neaten Paragraphs

When I’m constructing a list of items I often want to keep the lines together so that they are not split across a page.

By default a paragraph in Microsoft Word has trailing vertical whitespace. That means there’s a space below the last line of the paragraph that pushes the top of the next paragraph down the page, leaving a gap between paragraphs.

So when I have a list of items they look like the following when first imported:

- line 1
gap
- line 2
gap
- line 3
gap

But what I want is:

- line 1
- line 2
- line 3
gap

… and I want to ensure those three lines are not broken up over a page break.

To achieve this I highlight all the lines except the last line and run the following macro:

Sub ParagraphCutAndKeepWithNext()
'
' ParagraphCutAndKeepWithNext Macro
'
'
    With Selection.ParagraphFormat
        .SpaceAfter = 0
        .KeepWithNext = True
    End With
End Sub

Formatting a Splunk Time Field as ISO-8601

On occasion I will use the stats command in a Splunk search to identify the beginning and end of a particular set of events, e.g.:

search "Fatal Error"
|stats earliest(_time) as started, latest(_time) as ended by host

The problem is that this will display the “started” and “ended” fields as a number rather than a formatted time string. So to achieve this I use the fieldformat command with the strftime() function:

search "Fatal Error"
|stats earliest(_time) as started, latest(_time) as ended by host
|fieldformat started=strftime( started, "%Y-%m-%dT%H:%M:%S" )
|fieldformat ended=strftime( ended, "%Y-%m-%dT%H:%M:%S" )

Escaping a Backslash in Splunk

In a plain search string in Splunk a backslash is escaped by a single backslash, e.g. to search for MYDOMAIN\user123:

search MYDOMAIN\\user123

Inside a double-quoted regular expression string, however, the backslash also needs to be escaped with a backslash! e.g. to filter for MYDOMAIN\user123:

|regex _raw="MYDOMAIN\\\\[a-zA-Z]+"
|rex field=_raw "MYDOMAIN\\\\(?[a-zA-Z]+)"