Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
aa20c4b4
Commit
aa20c4b4
authored
Oct 21, 2016
by
David Ashpole
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
updated cadvisor godeps
parent
0006d661
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
163 additions
and
68 deletions
+163
-68
Godeps.json
Godeps/Godeps.json
+0
-0
fsHandler.go
.../github.com/google/cadvisor/container/common/fsHandler.go
+54
-39
handler.go
...or/github.com/google/cadvisor/container/docker/handler.go
+10
-7
handler.go
vendor/github.com/google/cadvisor/container/rkt/handler.go
+5
-3
fs.go
vendor/github.com/google/cadvisor/fs/fs.go
+61
-18
types.go
vendor/github.com/google/cadvisor/fs/types.go
+4
-1
container.go
vendor/github.com/google/cadvisor/info/v2/container.go
+4
-0
conversion.go
vendor/github.com/google/cadvisor/info/v2/conversion.go
+1
-0
manager.go
vendor/github.com/google/cadvisor/manager/manager.go
+24
-0
No files found.
Godeps/Godeps.json
View file @
aa20c4b4
This diff is collapsed.
Click to expand it.
vendor/github.com/google/cadvisor/container/common/fsHandler.go
View file @
aa20c4b4
...
...
@@ -16,6 +16,7 @@
package
common
import
(
"fmt"
"sync"
"time"
...
...
@@ -26,71 +27,85 @@ import (
type
FsHandler
interface
{
Start
()
Usage
()
(
baseUsageBytes
uint64
,
totalUsageBytes
uint64
)
Usage
()
FsUsage
Stop
()
}
type
FsUsage
struct
{
BaseUsageBytes
uint64
TotalUsageBytes
uint64
InodeUsage
uint64
}
type
realFsHandler
struct
{
sync
.
RWMutex
lastUpdate
time
.
Time
usageBytes
uint64
baseUsageBytes
uint64
period
time
.
Duration
minPeriod
time
.
Duration
rootfs
string
extraDir
string
fsInfo
fs
.
FsInfo
lastUpdate
time
.
Time
usage
FsUsage
period
time
.
Duration
minPeriod
time
.
Duration
rootfs
string
extraDir
string
fsInfo
fs
.
FsInfo
// Tells the container to stop.
stopChan
chan
struct
{}
}
const
(
long
Du
=
time
.
Second
duTimeout
=
time
.
Minute
max
Du
BackoffFactor
=
20
long
Op
=
time
.
Second
timeout
=
2
*
time
.
Minute
maxBackoffFactor
=
20
)
const
DefaultPeriod
=
time
.
Minute
var
_
FsHandler
=
&
realFsHandler
{}
func
NewFsHandler
(
period
time
.
Duration
,
rootfs
,
extraDir
string
,
fsInfo
fs
.
FsInfo
)
FsHandler
{
return
&
realFsHandler
{
lastUpdate
:
time
.
Time
{},
usageBytes
:
0
,
baseUsageBytes
:
0
,
period
:
period
,
minPeriod
:
period
,
rootfs
:
rootfs
,
extraDir
:
extraDir
,
fsInfo
:
fsInfo
,
stopChan
:
make
(
chan
struct
{},
1
),
lastUpdate
:
time
.
Time
{},
usage
:
FsUsage
{},
period
:
period
,
minPeriod
:
period
,
rootfs
:
rootfs
,
extraDir
:
extraDir
,
fsInfo
:
fsInfo
,
stopChan
:
make
(
chan
struct
{},
1
),
}
}
func
(
fh
*
realFsHandler
)
update
()
error
{
var
(
baseUsage
,
extraDirUsage
uint64
err
error
baseUsage
,
extraDirUsage
,
inodeUsage
uint64
rootDiskErr
,
rootInodeErr
,
extraDiskErr
error
)
// TODO(vishh): Add support for external mounts.
if
fh
.
rootfs
!=
""
{
baseUsage
,
err
=
fh
.
fsInfo
.
GetDirUsage
(
fh
.
rootfs
,
duTimeout
)
if
err
!=
nil
{
return
err
}
baseUsage
,
rootDiskErr
=
fh
.
fsInfo
.
GetDirDiskUsage
(
fh
.
rootfs
,
timeout
)
inodeUsage
,
rootInodeErr
=
fh
.
fsInfo
.
GetDirInodeUsage
(
fh
.
rootfs
,
timeout
)
}
if
fh
.
extraDir
!=
""
{
extraDirUsage
,
err
=
fh
.
fsInfo
.
GetDirUsage
(
fh
.
extraDir
,
duTimeout
)
if
err
!=
nil
{
return
err
}
extraDirUsage
,
extraDiskErr
=
fh
.
fsInfo
.
GetDirDiskUsage
(
fh
.
extraDir
,
timeout
)
}
// Wait to handle errors until after all operartions are run.
// An error in one will not cause an early return, skipping others
fh
.
Lock
()
defer
fh
.
Unlock
()
fh
.
lastUpdate
=
time
.
Now
()
fh
.
usageBytes
=
baseUsage
+
extraDirUsage
fh
.
baseUsageBytes
=
baseUsage
if
rootDiskErr
==
nil
&&
fh
.
rootfs
!=
""
{
fh
.
usage
.
InodeUsage
=
inodeUsage
}
if
rootInodeErr
==
nil
&&
fh
.
rootfs
!=
""
{
fh
.
usage
.
TotalUsageBytes
=
baseUsage
+
extraDirUsage
}
if
extraDiskErr
==
nil
&&
fh
.
extraDir
!=
""
{
fh
.
usage
.
BaseUsageBytes
=
baseUsage
}
// Combine errors into a single error to return
if
rootDiskErr
!=
nil
||
rootInodeErr
!=
nil
||
extraDiskErr
!=
nil
{
return
fmt
.
Errorf
(
"rootDiskErr: %v, rootInodeErr: %v, extraDiskErr: %v"
,
rootDiskErr
,
rootInodeErr
,
extraDiskErr
)
}
return
nil
}
...
...
@@ -105,15 +120,15 @@ func (fh *realFsHandler) trackUsage() {
if
err
:=
fh
.
update
();
err
!=
nil
{
glog
.
Errorf
(
"failed to collect filesystem stats - %v"
,
err
)
fh
.
period
=
fh
.
period
*
2
if
fh
.
period
>
max
Du
BackoffFactor
*
fh
.
minPeriod
{
fh
.
period
=
max
Du
BackoffFactor
*
fh
.
minPeriod
if
fh
.
period
>
maxBackoffFactor
*
fh
.
minPeriod
{
fh
.
period
=
maxBackoffFactor
*
fh
.
minPeriod
}
}
else
{
fh
.
period
=
fh
.
minPeriod
}
duration
:=
time
.
Since
(
start
)
if
duration
>
long
Du
{
glog
.
V
(
2
)
.
Infof
(
"
`du`
on following dirs took %v: %v"
,
duration
,
[]
string
{
fh
.
rootfs
,
fh
.
extraDir
})
if
duration
>
long
Op
{
glog
.
V
(
2
)
.
Infof
(
"
du and find
on following dirs took %v: %v"
,
duration
,
[]
string
{
fh
.
rootfs
,
fh
.
extraDir
})
}
}
}
...
...
@@ -127,8 +142,8 @@ func (fh *realFsHandler) Stop() {
close
(
fh
.
stopChan
)
}
func
(
fh
*
realFsHandler
)
Usage
()
(
baseUsageBytes
,
totalUsageBytes
uint64
)
{
func
(
fh
*
realFsHandler
)
Usage
()
FsUsage
{
fh
.
RLock
()
defer
fh
.
RUnlock
()
return
fh
.
baseUsageBytes
,
fh
.
usageBytes
return
fh
.
usage
}
vendor/github.com/google/cadvisor/container/docker/handler.go
View file @
aa20c4b4
...
...
@@ -243,7 +243,7 @@ func newDockerContainerHandler(
if
!
ignoreMetrics
.
Has
(
container
.
DiskUsageMetrics
)
{
handler
.
fsHandler
=
&
dockerFsHandler
{
fsHandler
:
common
.
NewFsHandler
(
time
.
Minute
,
rootfsStorageDir
,
otherStorageDir
,
fsInfo
),
fsHandler
:
common
.
NewFsHandler
(
common
.
DefaultPeriod
,
rootfsStorageDir
,
otherStorageDir
,
fsInfo
),
thinPoolWatcher
:
thinPoolWatcher
,
deviceID
:
handler
.
deviceID
,
}
...
...
@@ -283,8 +283,8 @@ func (h *dockerFsHandler) Stop() {
h
.
fsHandler
.
Stop
()
}
func
(
h
*
dockerFsHandler
)
Usage
()
(
uint64
,
uint64
)
{
baseUsage
,
usage
:=
h
.
fsHandler
.
Usage
()
func
(
h
*
dockerFsHandler
)
Usage
()
common
.
FsUsage
{
usage
:=
h
.
fsHandler
.
Usage
()
// When devicemapper is the storage driver, the base usage of the container comes from the thin pool.
// We still need the result of the fsHandler for any extra storage associated with the container.
...
...
@@ -299,12 +299,12 @@ func (h *dockerFsHandler) Usage() (uint64, uint64) {
// had at least 1 refresh and we still can't find the device.
glog
.
V
(
5
)
.
Infof
(
"unable to get fs usage from thin pool for device %s: %v"
,
h
.
deviceID
,
err
)
}
else
{
baseUsage
=
thinPoolUsage
usage
+=
thinPoolUsage
usage
.
BaseUsageBytes
=
thinPoolUsage
usage
.
TotalUsageBytes
+=
thinPoolUsage
}
}
return
baseUsage
,
usage
return
usage
}
func
(
self
*
dockerContainerHandler
)
Start
()
{
...
...
@@ -387,7 +387,10 @@ func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error
}
fsStat
:=
info
.
FsStats
{
Device
:
device
,
Type
:
fsType
,
Limit
:
limit
}
fsStat
.
BaseUsage
,
fsStat
.
Usage
=
self
.
fsHandler
.
Usage
()
usage
:=
self
.
fsHandler
.
Usage
()
fsStat
.
BaseUsage
=
usage
.
BaseUsageBytes
fsStat
.
Usage
=
usage
.
TotalUsageBytes
fsStat
.
Inodes
=
usage
.
InodeUsage
stats
.
Filesystem
=
append
(
stats
.
Filesystem
,
fsStat
)
...
...
vendor/github.com/google/cadvisor/container/rkt/handler.go
View file @
aa20c4b4
...
...
@@ -18,7 +18,6 @@ package rkt
import
(
"fmt"
"os"
"time"
rktapi
"github.com/coreos/rkt/api/v1alpha"
"github.com/google/cadvisor/container"
...
...
@@ -150,7 +149,7 @@ func newRktContainerHandler(name string, rktClient rktapi.PublicAPIClient, rktPa
}
if
!
ignoreMetrics
.
Has
(
container
.
DiskUsageMetrics
)
{
handler
.
fsHandler
=
common
.
NewFsHandler
(
time
.
Minute
,
rootfsStorageDir
,
""
,
fsInfo
)
handler
.
fsHandler
=
common
.
NewFsHandler
(
common
.
DefaultPeriod
,
rootfsStorageDir
,
""
,
fsInfo
)
}
return
handler
,
nil
...
...
@@ -228,7 +227,10 @@ func (handler *rktContainerHandler) getFsStats(stats *info.ContainerStats) error
fsStat
:=
info
.
FsStats
{
Device
:
deviceInfo
.
Device
,
Limit
:
limit
}
fsStat
.
BaseUsage
,
fsStat
.
Usage
=
handler
.
fsHandler
.
Usage
()
usage
:=
handler
.
fsHandler
.
Usage
()
fsStat
.
BaseUsage
=
usage
.
BaseUsageBytes
fsStat
.
Usage
=
usage
.
TotalUsageBytes
fsStat
.
Inodes
=
usage
.
InodeUsage
stats
.
Filesystem
=
append
(
stats
.
Filesystem
,
fsStat
)
...
...
vendor/github.com/google/cadvisor/fs/fs.go
View file @
aa20c4b4
...
...
@@ -19,6 +19,7 @@ package fs
import
(
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
...
...
@@ -44,24 +45,24 @@ const (
LabelRktImages
=
"rkt-images"
)
// The maximum number of `du` tasks that can be running at once.
const
maxCon
secutiveDu
s
=
20
// The maximum number of `du`
and `find`
tasks that can be running at once.
const
maxCon
currentOp
s
=
20
// A pool for restricting the number of consecutive `du` tasks running.
var
duPool
=
make
(
chan
struct
{},
maxConsecutiveDu
s
)
// A pool for restricting the number of consecutive `du`
and `find`
tasks running.
var
pool
=
make
(
chan
struct
{},
maxConcurrentOp
s
)
func
init
()
{
for
i
:=
0
;
i
<
maxCon
secutiveDu
s
;
i
++
{
release
Du
Token
()
for
i
:=
0
;
i
<
maxCon
currentOp
s
;
i
++
{
releaseToken
()
}
}
func
claim
Du
Token
()
{
<-
duP
ool
func
claimToken
()
{
<-
p
ool
}
func
release
Du
Token
()
{
duP
ool
<-
struct
{}{}
func
releaseToken
()
{
p
ool
<-
struct
{}{}
}
type
partition
struct
{
...
...
@@ -428,12 +429,12 @@ func (self *RealFsInfo) GetDirFsDevice(dir string) (*DeviceInfo, error) {
return
nil
,
fmt
.
Errorf
(
"could not find device with major: %d, minor: %d in cached partitions map"
,
major
,
minor
)
}
func
(
self
*
RealFsInfo
)
GetDirUsage
(
dir
string
,
timeout
time
.
Duration
)
(
uint64
,
error
)
{
func
(
self
*
RealFsInfo
)
GetDir
Disk
Usage
(
dir
string
,
timeout
time
.
Duration
)
(
uint64
,
error
)
{
if
dir
==
""
{
return
0
,
fmt
.
Errorf
(
"invalid directory"
)
}
claim
Du
Token
()
defer
release
Du
Token
()
claimToken
()
defer
releaseToken
()
cmd
:=
exec
.
Command
(
"nice"
,
"-n"
,
"19"
,
"du"
,
"-s"
,
dir
)
stdoutp
,
err
:=
cmd
.
StdoutPipe
()
if
err
!=
nil
{
...
...
@@ -447,21 +448,21 @@ func (self *RealFsInfo) GetDirUsage(dir string, timeout time.Duration) (uint64,
if
err
:=
cmd
.
Start
();
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"failed to exec du - %v"
,
err
)
}
stdoutb
,
souterr
:=
ioutil
.
ReadAll
(
stdoutp
)
stderrb
,
_
:=
ioutil
.
ReadAll
(
stderrp
)
timer
:=
time
.
AfterFunc
(
timeout
,
func
()
{
glog
.
Infof
(
"killing cmd %v due to timeout(%s)"
,
cmd
.
Args
,
timeout
.
String
())
cmd
.
Process
.
Kill
()
})
stdoutb
,
souterr
:=
ioutil
.
ReadAll
(
stdoutp
)
if
souterr
!=
nil
{
glog
.
Errorf
(
"failed to read from stdout for cmd %v - %v"
,
cmd
.
Args
,
souterr
)
}
stderrb
,
_
:=
ioutil
.
ReadAll
(
stderrp
)
err
=
cmd
.
Wait
()
timer
.
Stop
()
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"du command failed on %s with output stdout: %s, stderr: %s - %v"
,
dir
,
string
(
stdoutb
),
string
(
stderrb
),
err
)
}
stdout
:=
string
(
stdoutb
)
if
souterr
!=
nil
{
glog
.
Errorf
(
"failed to read from stdout for cmd %v - %v"
,
cmd
.
Args
,
souterr
)
}
usageInKb
,
err
:=
strconv
.
ParseUint
(
strings
.
Fields
(
stdout
)[
0
],
10
,
64
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"cannot parse 'du' output %s - %s"
,
stdout
,
err
)
...
...
@@ -469,6 +470,48 @@ func (self *RealFsInfo) GetDirUsage(dir string, timeout time.Duration) (uint64,
return
usageInKb
*
1024
,
nil
}
func
(
self
*
RealFsInfo
)
GetDirInodeUsage
(
dir
string
,
timeout
time
.
Duration
)
(
uint64
,
error
)
{
if
dir
==
""
{
return
0
,
fmt
.
Errorf
(
"invalid directory"
)
}
var
stdout
,
stdwcerr
,
stdfinderr
bytes
.
Buffer
var
err
error
claimToken
()
defer
releaseToken
()
findCmd
:=
exec
.
Command
(
"find"
,
dir
,
"-xdev"
,
"-printf"
,
"."
)
wcCmd
:=
exec
.
Command
(
"wc"
,
"-c"
)
if
wcCmd
.
Stdin
,
err
=
findCmd
.
StdoutPipe
();
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"failed to setup stdout for cmd %v - %v"
,
findCmd
.
Args
,
err
)
}
wcCmd
.
Stdout
,
wcCmd
.
Stderr
,
findCmd
.
Stderr
=
&
stdout
,
&
stdwcerr
,
&
stdfinderr
if
err
=
findCmd
.
Start
();
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"failed to exec cmd %v - %v; stderr: %v"
,
findCmd
.
Args
,
err
,
stdfinderr
.
String
())
}
if
err
=
wcCmd
.
Start
();
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"failed to exec cmd %v - %v; stderr %v"
,
wcCmd
.
Args
,
err
,
stdwcerr
.
String
())
}
timer
:=
time
.
AfterFunc
(
timeout
,
func
()
{
glog
.
Infof
(
"killing cmd %v, and cmd %v due to timeout(%s)"
,
findCmd
.
Args
,
wcCmd
.
Args
,
timeout
.
String
())
wcCmd
.
Process
.
Kill
()
findCmd
.
Process
.
Kill
()
})
err
=
findCmd
.
Wait
()
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"cmd %v failed. stderr: %s; err: %v"
,
findCmd
.
Args
,
stdfinderr
.
String
(),
err
)
}
err
=
wcCmd
.
Wait
()
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"cmd %v failed. stderr: %s; err: %v"
,
wcCmd
.
Args
,
stdwcerr
.
String
(),
err
)
}
timer
.
Stop
()
inodeUsage
,
err
:=
strconv
.
ParseUint
(
strings
.
TrimSpace
(
stdout
.
String
()),
10
,
64
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"cannot parse cmds: %v, %v output %s - %s"
,
findCmd
.
Args
,
wcCmd
.
Args
,
stdout
.
String
(),
err
)
}
return
inodeUsage
,
nil
}
func
getVfsStats
(
path
string
)
(
total
uint64
,
free
uint64
,
avail
uint64
,
inodes
uint64
,
inodesFree
uint64
,
err
error
)
{
var
s
syscall
.
Statfs_t
if
err
=
syscall
.
Statfs
(
path
,
&
s
);
err
!=
nil
{
...
...
vendor/github.com/google/cadvisor/fs/types.go
View file @
aa20c4b4
...
...
@@ -67,7 +67,10 @@ type FsInfo interface {
GetFsInfoForPath
(
mountSet
map
[
string
]
struct
{})
([]
Fs
,
error
)
// Returns number of bytes occupied by 'dir'.
GetDirUsage
(
dir
string
,
timeout
time
.
Duration
)
(
uint64
,
error
)
GetDirDiskUsage
(
dir
string
,
timeout
time
.
Duration
)
(
uint64
,
error
)
// Returns number of inodes used by 'dir'.
GetDirInodeUsage
(
dir
string
,
timeout
time
.
Duration
)
(
uint64
,
error
)
// Returns the block device info of the filesystem on which 'dir' resides.
GetDirFsDevice
(
dir
string
)
(
*
DeviceInfo
,
error
)
...
...
vendor/github.com/google/cadvisor/info/v2/container.go
View file @
aa20c4b4
...
...
@@ -301,4 +301,8 @@ type FilesystemStats struct {
TotalUsageBytes
*
uint64
`json:"totalUsageBytes,omitempty"`
// Number of bytes consumed by a container through its root filesystem.
BaseUsageBytes
*
uint64
`json:"baseUsageBytes,omitempty"`
// Number of inodes used within the container's root filesystem.
// This only accounts for inodes that are shared across containers,
// and does not include inodes used in mounted directories.
InodeUsage
*
uint64
`json:"containter_inode_usage,omitempty"`
}
vendor/github.com/google/cadvisor/info/v2/conversion.go
View file @
aa20c4b4
...
...
@@ -127,6 +127,7 @@ func ContainerStatsFromV1(spec *v1.ContainerSpec, stats []*v1.ContainerStats) []
stat
.
Filesystem
=
&
FilesystemStats
{
TotalUsageBytes
:
&
val
.
Filesystem
[
0
]
.
Usage
,
BaseUsageBytes
:
&
val
.
Filesystem
[
0
]
.
BaseUsage
,
InodeUsage
:
&
val
.
Filesystem
[
0
]
.
Inodes
,
}
}
else
if
len
(
val
.
Filesystem
)
>
1
{
// Cannot handle multiple devices per container.
...
...
vendor/github.com/google/cadvisor/manager/manager.go
View file @
aa20c4b4
...
...
@@ -101,6 +101,9 @@ type Manager interface {
// Get version information about different components we depend on.
GetVersionInfo
()
(
*
info
.
VersionInfo
,
error
)
// Get filesystem information for the filesystem that contains the given directory
GetDirFsInfo
(
dir
string
)
(
v2
.
FsInfo
,
error
)
// Get filesystem information for a given label.
// Returns information for all global filesystems if label is empty.
GetFsInfo
(
label
string
)
([]
v2
.
FsInfo
,
error
)
...
...
@@ -657,6 +660,27 @@ func (self *manager) getRequestedContainers(containerName string, options v2.Req
return
containersMap
,
nil
}
func
(
self
*
manager
)
GetDirFsInfo
(
dir
string
)
(
v2
.
FsInfo
,
error
)
{
dirDevice
,
err
:=
self
.
fsInfo
.
GetDirFsDevice
(
dir
)
if
err
!=
nil
{
return
v2
.
FsInfo
{},
fmt
.
Errorf
(
"error trying to get filesystem Device for dir %v: err: %v"
,
dir
,
err
)
}
dirMountpoint
,
err
:=
self
.
fsInfo
.
GetMountpointForDevice
(
dirDevice
.
Device
)
if
err
!=
nil
{
return
v2
.
FsInfo
{},
fmt
.
Errorf
(
"error trying to get MountPoint for Root Device: %v, err: %v"
,
dirDevice
,
err
)
}
infos
,
err
:=
self
.
GetFsInfo
(
""
)
if
err
!=
nil
{
return
v2
.
FsInfo
{},
err
}
for
_
,
info
:=
range
infos
{
if
info
.
Mountpoint
==
dirMountpoint
{
return
info
,
nil
}
}
return
v2
.
FsInfo
{},
fmt
.
Errorf
(
"did not find fs info for dir: %v"
,
dir
)
}
func
(
self
*
manager
)
GetFsInfo
(
label
string
)
([]
v2
.
FsInfo
,
error
)
{
var
empty
time
.
Time
// Get latest data from filesystems hanging off root container.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment