ruby-static-tracing 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/ext/ruby-static-tracing/darwin/provider.c +128 -0
- data/ext/ruby-static-tracing/darwin/provider.h +62 -0
- data/ext/ruby-static-tracing/darwin/ruby_static_tracing.c +48 -0
- data/ext/ruby-static-tracing/darwin/tracepoint.c +231 -0
- data/ext/ruby-static-tracing/darwin/tracepoint.h +52 -0
- data/ext/ruby-static-tracing/extconf.rb +45 -5
- data/ext/ruby-static-tracing/{linux → include}/ruby_static_tracing.h +3 -6
- data/ext/ruby-static-tracing/lib/deps-extconf.rb +47 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/example/demo.c +42 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/dynamic-symbols.c +41 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/dynamic-symbols.h +34 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/errors.c +30 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/errors.h +8 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/hash-table.c +27 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/hash-table.h +3 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/libstapsdt.c +208 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/libstapsdt.h +66 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/sdtnote.c +176 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/sdtnote.h +46 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/section.c +30 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/section.h +21 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/shared-lib.c +563 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/shared-lib.h +46 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/string-table.c +67 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/string-table.h +28 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/util.c +12 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/util.h +6 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/tests/test-errors.c +77 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/tests/test-memory-leaks.c +25 -0
- data/ext/ruby-static-tracing/lib/post-extconf.rb +37 -0
- data/ext/ruby-static-tracing/linux/provider.c +1 -1
- data/ext/ruby-static-tracing/linux/provider.h +2 -7
- data/ext/ruby-static-tracing/linux/ruby_static_tracing.c +4 -3
- data/ext/ruby-static-tracing/linux/tracepoint.c +0 -1
- data/ext/ruby-static-tracing/linux/tracepoint.h +1 -5
- data/ext/ruby-static-tracing/linux/types.h +11 -0
- data/lib/ruby-static-tracing.rb +5 -7
- data/lib/ruby-static-tracing/platform.rb +48 -0
- data/lib/ruby-static-tracing/provider.rb +18 -1
- data/lib/ruby-static-tracing/tracepoint.rb +15 -3
- data/lib/ruby-static-tracing/tracepoints.rb +36 -0
- data/lib/ruby-static-tracing/tracers.rb +1 -0
- data/lib/ruby-static-tracing/tracers/base.rb +68 -0
- data/lib/ruby-static-tracing/tracers/helpers.rb +17 -0
- data/lib/ruby-static-tracing/tracers/latency_tracer.rb +13 -60
- data/lib/ruby-static-tracing/tracers/stack_tracer.rb +19 -0
- data/lib/ruby-static-tracing/version.rb +1 -1
- metadata +41 -5
@@ -0,0 +1,46 @@
|
|
1
|
+
#ifndef _SHARED_LIB_H
|
2
|
+
#define _SHARED_LIB_H
|
3
|
+
|
4
|
+
#include "section.h"
|
5
|
+
#include "string-table.h"
|
6
|
+
#include "sdtnote.h"
|
7
|
+
#include "dynamic-symbols.h"
|
8
|
+
|
9
|
+
typedef struct {
|
10
|
+
Section
|
11
|
+
*hash,
|
12
|
+
*dynSym,
|
13
|
+
*dynStr,
|
14
|
+
*text,
|
15
|
+
*sdtBase,
|
16
|
+
*ehFrame,
|
17
|
+
*dynamic,
|
18
|
+
*sdtNote,
|
19
|
+
*shStrTab;
|
20
|
+
} SectionsList;
|
21
|
+
|
22
|
+
typedef struct {
|
23
|
+
Elf *elf;
|
24
|
+
Elf64_Ehdr *ehdr;
|
25
|
+
Elf64_Phdr *phdrLoad1, *phdrLoad2, *phdrDyn, *phdrStack;
|
26
|
+
|
27
|
+
StringTable *stringTable;
|
28
|
+
StringTable *dynamicString;
|
29
|
+
|
30
|
+
DynamicSymbolTable *dynamicSymbols;
|
31
|
+
|
32
|
+
SDTNoteList_t *sdtNotes;
|
33
|
+
size_t sdtNotesCount;
|
34
|
+
|
35
|
+
SectionsList sections;
|
36
|
+
} DynElf;
|
37
|
+
|
38
|
+
DynElf *dynElfInit();
|
39
|
+
|
40
|
+
int dynElfAddProbe(DynElf *dynElf, SDTProbe_t *probe);
|
41
|
+
|
42
|
+
int dynElfSave(DynElf *dynElf);
|
43
|
+
|
44
|
+
void dynElfClose(DynElf *dynElf);
|
45
|
+
|
46
|
+
#endif
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
|
4
|
+
#include "string-table.h"
|
5
|
+
|
6
|
+
StringTable *stringTableInit() {
|
7
|
+
StringTable *stringTable = (StringTable *)calloc(sizeof(StringTable), 1);
|
8
|
+
stringTable->count = 1;
|
9
|
+
stringTable->size = 1;
|
10
|
+
|
11
|
+
stringTable->first = (StringTableNode *)calloc(sizeof(StringTableNode), 1);
|
12
|
+
|
13
|
+
stringTable->first->index = 0;
|
14
|
+
stringTable->first->size = 1;
|
15
|
+
stringTable->first->str = (char *)calloc(sizeof(char), 1);
|
16
|
+
stringTable->first->str[0] = '\0';
|
17
|
+
stringTable->first->next = NULL;
|
18
|
+
|
19
|
+
return stringTable;
|
20
|
+
}
|
21
|
+
|
22
|
+
StringTableNode *stringTableAdd(StringTable *stringTable, char *str) {
|
23
|
+
StringTableNode *current;
|
24
|
+
|
25
|
+
for (current = stringTable->first; current->next != NULL;
|
26
|
+
current = current->next) {
|
27
|
+
}
|
28
|
+
|
29
|
+
current->next = (StringTableNode *)calloc(sizeof(StringTableNode), 1);
|
30
|
+
current->next->index = current->index + current->size;
|
31
|
+
|
32
|
+
current = current->next;
|
33
|
+
current->size = strlen(str) + 1;
|
34
|
+
|
35
|
+
current->str = (char *)calloc(current->size, 1);
|
36
|
+
memcpy(current->str, str, current->size);
|
37
|
+
current->next = NULL;
|
38
|
+
|
39
|
+
stringTable->count += 1;
|
40
|
+
stringTable->size += current->size;
|
41
|
+
|
42
|
+
return current;
|
43
|
+
}
|
44
|
+
|
45
|
+
char *stringTableToBuffer(StringTable *stringTable) {
|
46
|
+
int offset;
|
47
|
+
StringTableNode *current;
|
48
|
+
char *buffer = (char *)calloc(stringTable->size, 1);
|
49
|
+
|
50
|
+
for (current = stringTable->first, offset = 0; current != NULL;
|
51
|
+
offset += current->size, current = current->next) {
|
52
|
+
memcpy(&buffer[offset], current->str, current->size);
|
53
|
+
}
|
54
|
+
|
55
|
+
return buffer;
|
56
|
+
}
|
57
|
+
|
58
|
+
void stringTableFree(StringTable *table) {
|
59
|
+
StringTableNode *node=NULL, *next=NULL;
|
60
|
+
for(node=table->first; node!=NULL; node=next) {
|
61
|
+
free(node->str);
|
62
|
+
|
63
|
+
next=node->next;
|
64
|
+
free(node);
|
65
|
+
}
|
66
|
+
free(table);
|
67
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#ifndef _STRING_TABLE_H
|
2
|
+
#define _STRING_TABLE_H
|
3
|
+
|
4
|
+
#include <stdlib.h>
|
5
|
+
#include <string.h>
|
6
|
+
|
7
|
+
typedef struct StringTableNode_ {
|
8
|
+
int index;
|
9
|
+
int size;
|
10
|
+
char *str;
|
11
|
+
struct StringTableNode_ *next;
|
12
|
+
} StringTableNode;
|
13
|
+
|
14
|
+
typedef struct {
|
15
|
+
int count;
|
16
|
+
size_t size;
|
17
|
+
StringTableNode *first;
|
18
|
+
} StringTable;
|
19
|
+
|
20
|
+
StringTable *stringTableInit();
|
21
|
+
|
22
|
+
StringTableNode *stringTableAdd(StringTable *stringTable, char *str);
|
23
|
+
|
24
|
+
char *stringTableToBuffer(StringTable *stringTable);
|
25
|
+
|
26
|
+
void stringTableFree(StringTable *stringTable);
|
27
|
+
|
28
|
+
#endif
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#include "libstapsdt.h"
|
2
|
+
#include <stdio.h>
|
3
|
+
#include <unistd.h>
|
4
|
+
#include <dlfcn.h>
|
5
|
+
|
6
|
+
int testElfCreationError() {
|
7
|
+
// TODO (mmarchini) write test case for elf creation error
|
8
|
+
return 1;
|
9
|
+
}
|
10
|
+
|
11
|
+
int testTmpCreationError() {
|
12
|
+
SDTProvider_t *provider;
|
13
|
+
SDTError_t errno;
|
14
|
+
provider = providerInit("test/probe/creation/error");
|
15
|
+
if(providerLoad(provider) == 0) {
|
16
|
+
return 0;
|
17
|
+
}
|
18
|
+
printf("[testTmpCreationError] Error message: %s\n", provider->error);
|
19
|
+
errno = provider->errno;
|
20
|
+
providerDestroy(provider);
|
21
|
+
return errno == tmpCreationError;
|
22
|
+
}
|
23
|
+
|
24
|
+
int testSharedLibraryOpenError() {
|
25
|
+
// TODO (mmarchini) write test case for shared library loading error
|
26
|
+
return 1;
|
27
|
+
}
|
28
|
+
|
29
|
+
int testSymbolLoadingError() {
|
30
|
+
// TODO (mmarchini) write test case for symbol loading error
|
31
|
+
return 1;
|
32
|
+
}
|
33
|
+
|
34
|
+
int testSharedLibraryCloseError() {
|
35
|
+
SDTProvider_t *provider;
|
36
|
+
SDTError_t errno;
|
37
|
+
provider = providerInit("test-error");
|
38
|
+
providerLoad(provider);
|
39
|
+
dlclose(provider->_handle);
|
40
|
+
if(providerUnload(provider) == 0) {
|
41
|
+
return 0;
|
42
|
+
}
|
43
|
+
errno = provider->errno;
|
44
|
+
printf("[testSharedLibraryCloseError] Error message: %s\n", provider->error);
|
45
|
+
providerDestroy(provider);
|
46
|
+
return errno == sharedLibraryCloseError;
|
47
|
+
}
|
48
|
+
|
49
|
+
int main() {
|
50
|
+
if (!testElfCreationError()) {
|
51
|
+
printf("Test case failed: testElfCreationError\n");
|
52
|
+
return -1;
|
53
|
+
}
|
54
|
+
|
55
|
+
if (!testTmpCreationError()) {
|
56
|
+
printf("Test case failed: testTmpCreationError\n");
|
57
|
+
return -2;
|
58
|
+
}
|
59
|
+
|
60
|
+
if (!testSharedLibraryOpenError()) {
|
61
|
+
printf("Test case failed: testSharedLibraryOpenError\n");
|
62
|
+
return -3;
|
63
|
+
}
|
64
|
+
|
65
|
+
if (!testSymbolLoadingError()) {
|
66
|
+
printf("Test case failed: testSymbolLoadingError\n");
|
67
|
+
return -4;
|
68
|
+
}
|
69
|
+
|
70
|
+
if (!testSharedLibraryCloseError()) {
|
71
|
+
printf("Test case failed: testSharedLibraryCloseError\n");
|
72
|
+
return -5;
|
73
|
+
}
|
74
|
+
|
75
|
+
|
76
|
+
return 0;
|
77
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <unistd.h>
|
3
|
+
#include <libstapsdt.h>
|
4
|
+
|
5
|
+
int main( int argc, char *argv[] ) {
|
6
|
+
SDTProvider_t *provider;
|
7
|
+
SDTProbe_t *probe1, *probe2;
|
8
|
+
|
9
|
+
provider = providerInit("testProvider");
|
10
|
+
probe1 = providerAddProbe(provider, "testProbe1", 4, int8, uint8, int64, uint64);
|
11
|
+
probe2 = providerAddProbe(provider, "testProbe2", 2, int8, uint8);
|
12
|
+
|
13
|
+
if(providerLoad(provider) == -1) {
|
14
|
+
printf("Something went wrong...\n");
|
15
|
+
return -1;
|
16
|
+
}
|
17
|
+
|
18
|
+
probeFire(probe1, 1, 2, 3, 4);
|
19
|
+
probeFire(probe2, -3, 8);
|
20
|
+
|
21
|
+
providerUnload(provider);
|
22
|
+
providerDestroy(provider);
|
23
|
+
|
24
|
+
return 0;
|
25
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__)
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
require 'ruby-static-tracing/platform'
|
5
|
+
|
6
|
+
BASE_DIR = File.expand_path(File.dirname(__FILE__))
|
7
|
+
LIB_DIR = File.expand_path('../../../../lib/ruby-static-tracing', __FILE__)
|
8
|
+
|
9
|
+
# Linux is a noop
|
10
|
+
if StaticTracing::Platform.linux?
|
11
|
+
File.write "Makefile", <<MAKEFILE
|
12
|
+
all:
|
13
|
+
touch post.so
|
14
|
+
clean:
|
15
|
+
install:
|
16
|
+
MAKEFILE
|
17
|
+
exit
|
18
|
+
# We'll build libusdt and install and update linker info
|
19
|
+
elsif StaticTracing::Platform.darwin?
|
20
|
+
# This is done to ensure that the bundle will look in its local directory for the library
|
21
|
+
File.write "Makefile", <<MAKEFILE
|
22
|
+
all:
|
23
|
+
touch post.bundle
|
24
|
+
install_name_tool -change libusdt.dylib @loader_path/../ruby-static-tracing/libusdt.dylib #{File.join(LIB_DIR, 'ruby_static_tracing.bundle')}
|
25
|
+
clean:
|
26
|
+
install:
|
27
|
+
MAKEFILE
|
28
|
+
exit
|
29
|
+
else
|
30
|
+
# - Stub, for other platforms that we don't support, we write an empty makefile
|
31
|
+
File.write "Makefile", <<MAKEFILE
|
32
|
+
all:
|
33
|
+
clean:
|
34
|
+
install:
|
35
|
+
MAKEFILE
|
36
|
+
exit
|
37
|
+
end
|
@@ -19,7 +19,7 @@ provider_initialize(VALUE self, VALUE name)
|
|
19
19
|
// Check and cast arguments
|
20
20
|
c_name_str = check_name_arg(name);
|
21
21
|
|
22
|
-
// Build
|
22
|
+
// Build provider structure
|
23
23
|
TypedData_Get_Struct(self, static_tracing_provider_t, &static_tracing_provider_type, res);
|
24
24
|
res->sdt_provider = providerInit(c_name_str);
|
25
25
|
return self;
|
@@ -1,16 +1,11 @@
|
|
1
|
-
/*
|
2
|
-
For core static tracing functions exposed directly to ruby.
|
3
|
-
Functions here are associated with rubyland operations.
|
4
|
-
*/
|
5
1
|
#ifndef STATIC_TRACING_PROVIDER_H
|
6
2
|
#define STATIC_TRACING_PROVIDER_H
|
7
3
|
|
8
|
-
#include <ruby.h>
|
9
4
|
// Include libstapsdt.h to wrap
|
10
|
-
#include <libstapsdt.h>
|
5
|
+
#include <libstapsdt.h> // FIXME use local
|
11
6
|
|
7
|
+
#include "types.h"
|
12
8
|
#include "ruby_static_tracing.h"
|
13
|
-
#include "tracepoint.h"
|
14
9
|
|
15
10
|
typedef struct {
|
16
11
|
char *name;
|
@@ -1,5 +1,6 @@
|
|
1
|
-
#include "
|
2
|
-
|
1
|
+
#include "ruby_static_tracing.h"
|
2
|
+
|
3
|
+
VALUE eUSDT, eInternal;
|
3
4
|
|
4
5
|
void Init_ruby_static_tracing()
|
5
6
|
{
|
@@ -41,7 +42,7 @@ void Init_ruby_static_tracing()
|
|
41
42
|
|
42
43
|
rb_define_alloc_func(cTracepoint, static_tracing_tracepoint_alloc);
|
43
44
|
rb_define_method(cTracepoint, "tracepoint_initialize", tracepoint_initialize, 3);
|
44
|
-
rb_define_method(cTracepoint, "
|
45
|
+
rb_define_method(cTracepoint, "_fire_tracepoint", tracepoint_fire, 1);
|
45
46
|
rb_define_method(cTracepoint, "enabled?", tracepoint_enabled, 0);
|
46
47
|
}
|
47
48
|
|
@@ -1,16 +1,12 @@
|
|
1
|
-
/*
|
2
|
-
For core static tracing functions exposed directly to ruby.
|
3
|
-
Functions here are associated with rubyland operations.
|
4
|
-
*/
|
5
1
|
#ifndef STATIC_TRACING_TRACEPOINT_H
|
6
2
|
#define STATIC_TRACING_TRACEPOINT_H
|
7
3
|
|
8
4
|
#include <ruby.h>
|
9
5
|
// Include libstapsdt.h to wrap
|
10
6
|
#include <libstapsdt.h>
|
11
|
-
// Probably need to include provider.h to be able to initialize self
|
12
7
|
|
13
8
|
#include "ruby_static_tracing.h"
|
9
|
+
#include "types.h"
|
14
10
|
|
15
11
|
typedef union {
|
16
12
|
unsigned long long intval;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#ifndef STATIC_TRACING_TYPES_H
|
2
|
+
#define STATIC_TRACING_TYPES_H
|
3
|
+
|
4
|
+
#include <libstapsdt.h>
|
5
|
+
|
6
|
+
typedef enum TRACEPOINT_ARG_TYPES_ENUM {
|
7
|
+
Integer = int64, // STAP enum type -8
|
8
|
+
String = uint64, // STAP enum type 8
|
9
|
+
} Tracepoint_arg_types;
|
10
|
+
|
11
|
+
#endif // STATIC_TRACING_TYPEs_H
|
data/lib/ruby-static-tracing.rb
CHANGED
@@ -6,6 +6,7 @@ require 'ruby-static-tracing/version'
|
|
6
6
|
require 'ruby-static-tracing/platform'
|
7
7
|
require 'ruby-static-tracing/provider'
|
8
8
|
require 'ruby-static-tracing/tracepoint'
|
9
|
+
require 'ruby-static-tracing/tracepoints'
|
9
10
|
require 'ruby-static-tracing/configuration'
|
10
11
|
require 'ruby-static-tracing/tracers'
|
11
12
|
|
@@ -45,12 +46,15 @@ module StaticTracing
|
|
45
46
|
# with a wrapped version that has tracing enabled
|
46
47
|
def enable!
|
47
48
|
tracers.each(&:enable!)
|
49
|
+
StaticTracing::Provider.enable!
|
48
50
|
@enabled = true
|
49
51
|
end
|
50
52
|
|
51
53
|
# Overwrite the definition of all functions to their original definition,
|
52
54
|
# no longer wrapping them
|
53
55
|
def disable!
|
56
|
+
tracers.each(&:disable!)
|
57
|
+
StaticTracing::Provider.disable!
|
54
58
|
@enabled = false
|
55
59
|
end
|
56
60
|
|
@@ -72,10 +76,4 @@ module StaticTracing
|
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
75
|
-
|
76
|
-
# within a trap handler.
|
77
|
-
# Specify default signals, but allow these to be overidden for easier integration
|
78
|
-
|
79
|
-
# This loads the actual C extension, we might want to guard it
|
80
|
-
# for cases where the extension isn't yet built
|
81
|
-
require 'ruby-static-tracing/ruby_static_tracing' if StaticTracing::Platform.linux?
|
79
|
+
require 'ruby-static-tracing/ruby_static_tracing' if StaticTracing::Platform.linux? || StaticTracing::Platform.darwin?
|
@@ -3,9 +3,57 @@
|
|
3
3
|
module StaticTracing
|
4
4
|
module Platform
|
5
5
|
extend self
|
6
|
+
UNSUPPORTED_POST_INSTALL_MESSAGE = %(
|
7
|
+
WARNING: You have installed this on an unsupported platform, somehow.
|
8
|
+
|
9
|
+
You should verify for yourself that the behavior on your platform is safe.
|
10
|
+
)
|
11
|
+
|
12
|
+
LINUX_POST_INSTALL_MESSAGE = %(
|
13
|
+
WARNING: you will need a new kernel (4.14+) that supports eBPF.
|
14
|
+
|
15
|
+
You should use the newest possible version of bpftrace
|
16
|
+
).freeze
|
17
|
+
|
18
|
+
DARWIN_POST_INSTALL_MESSAGE = %(
|
19
|
+
WARNING: tracing with dtrace will not work with SIP enabled.
|
20
|
+
|
21
|
+
SIP is enabled by default on recent versions of OSX. You can
|
22
|
+
check if SIP is enabled with:
|
23
|
+
|
24
|
+
csrutil status
|
25
|
+
|
26
|
+
If you want to test your probes out locally, you will need to at
|
27
|
+
least allow dtrace. To do this, you must reboot into recovery mode
|
28
|
+
by holding CMD + R while your Mac is booting. Once it has booted,
|
29
|
+
open a terminal and type:
|
30
|
+
|
31
|
+
csrutil clear
|
32
|
+
csrutil enable --without-dtrace
|
33
|
+
|
34
|
+
After this, you should be able to use dtrace on the tracepoints you
|
35
|
+
define here. If it still doesn't work, you can disable SIP entirely
|
36
|
+
but this is not recommended for security purposes.
|
37
|
+
).freeze
|
6
38
|
|
7
39
|
def linux?
|
8
40
|
/linux/.match(RUBY_PLATFORM)
|
9
41
|
end
|
42
|
+
|
43
|
+
def darwin?
|
44
|
+
/darwin/.match(RUBY_PLATFORM)
|
45
|
+
end
|
46
|
+
|
47
|
+
def post_install_message
|
48
|
+
message = begin
|
49
|
+
if linux?
|
50
|
+
LINUX_POST_INSTALL_MESSAGE
|
51
|
+
elsif darwin?
|
52
|
+
DARWIN_POST_INSTALL_MESSAGE
|
53
|
+
else
|
54
|
+
UNSUPPORTED_POST_INSTALL_MESSAGE
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
10
58
|
end
|
11
59
|
end
|