Skip to content

Commit 2118a95

Browse files
committed
Cirron v0.2
* Cirron now attempts to measure its own overhead and remove it from the resulting measurement * Added recording of branch misses and page faults and made it simpler to add more events
1 parent 37b03f1 commit 2118a95

File tree

3 files changed

+132
-19
lines changed

3 files changed

+132
-19
lines changed

cirron/cirron.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,23 @@
1313

1414
class Counter(Structure):
1515
_fields_ = [
16-
("instruction_count", c_uint64),
1716
("time_enabled_ns", c_uint64),
17+
("instruction_count", c_uint64),
18+
("branch_misses", c_uint64),
19+
("page_faults", c_uint64),
1820
]
1921

2022
def __str__(self):
21-
return f"Counter(instruction_count={self.instruction_count}, time_enabled_ns={self.time_enabled_ns})"
23+
return self.__repr__()
2224

2325
def __repr__(self):
24-
return f"Counter(instruction_count={self.instruction_count}, time_enabled_ns={self.time_enabled_ns})"
26+
repr = "Counter("
27+
28+
for field, _ in Counter._fields_:
29+
repr += f"{field}={getattr(self, field)}, "
30+
repr = repr[:-2] + ")"
31+
32+
return repr
2533

2634

2735
lib_path = resource_filename(__name__, "cirronlib.so")
@@ -55,4 +63,29 @@ def end(self):
5563
ret_val = cirron_lib.end(self.fd, byref(self.counter))
5664
if ret_val == -1:
5765
raise Exception("Failed to end collector.")
66+
67+
global overhead
68+
if overhead:
69+
for field, _ in Counter._fields_:
70+
setattr(
71+
self.counter, field, getattr(self.counter, field) - overhead[field]
72+
)
5873
return self.counter
74+
75+
76+
# We try to estimate what the overhead of the collector is, taking the minimum
77+
# of 10 runs.
78+
overhead = {}
79+
collector = Collector()
80+
o = {}
81+
for _ in range(10):
82+
collector.start()
83+
collector.end()
84+
85+
for field, _ in Counter._fields_:
86+
if field not in overhead:
87+
o[field] = getattr(collector.counter, field)
88+
else:
89+
o[field] = min(overhead[field], getattr(collector.counter, field))
90+
overhead = o
91+
del collector

cirron/cirronlib.c

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,87 @@
66
#include <errno.h>
77
#include <sys/ioctl.h>
88
#include <stdint.h>
9+
#include <stdlib.h>
910

1011
struct counter
1112
{
12-
uint64_t instruction_count;
1313
uint64_t time_enabled_ns;
14+
uint64_t instruction_count;
15+
uint64_t branch_misses;
16+
uint64_t page_faults;
17+
};
18+
19+
struct read_format
20+
{
21+
uint64_t nr;
22+
uint64_t time_enabled;
23+
uint64_t time_running;
24+
struct
25+
{
26+
uint64_t value;
27+
} values[];
28+
};
29+
30+
struct perf_event_config
31+
{
32+
uint64_t type;
33+
uint64_t config;
34+
};
35+
36+
struct perf_event_config events[] = {
37+
{PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS},
38+
{PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES},
39+
{PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS},
40+
// {PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES}, For whatever reason, these two always show 0
41+
// {PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS},
1442
};
1543

44+
const int NUM_EVENTS = sizeof(events) / sizeof(events[0]);
45+
1646
int start()
1747
{
48+
// Construct base perf_event_attr struct
1849
struct perf_event_attr attr;
1950
memset(&attr, 0, sizeof(attr));
20-
attr.type = PERF_TYPE_HARDWARE;
2151
attr.size = sizeof(attr);
52+
attr.disabled = 1;
2253
attr.exclude_kernel = 1;
2354
attr.exclude_hv = 1;
2455
attr.sample_period = 0;
25-
attr.config = PERF_COUNT_HW_INSTRUCTIONS;
26-
attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
56+
attr.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
57+
58+
int group = -1;
59+
int leader_fd;
60+
61+
// Enable every event in perf_event_config
62+
for (int i = 0; i < NUM_EVENTS; i++)
63+
{
64+
attr.type = events[i].type;
65+
attr.config = events[i].config;
66+
67+
int fd = syscall(SYS_perf_event_open, &attr, 0, -1, group, 0);
68+
if (fd == -1)
69+
{
70+
fprintf(stderr, "Failed to open event %lu: %s.\n", events[i].config, strerror(errno));
71+
return -1;
72+
}
73+
74+
if (i == 0)
75+
{
76+
group = fd;
77+
leader_fd = fd;
78+
}
79+
}
2780

28-
int fd = syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0);
29-
if (fd == -1)
81+
// Enable the event group
82+
if (ioctl(leader_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1)
3083
{
31-
fprintf(stderr, "Failed to perf_event_open: %s.\n", strerror(errno));
84+
fprintf(stderr, "Failed to enable perf events: %s.\n", strerror(errno));
85+
// Consider cleaning up previously opened file descriptors here
3286
return -1;
3387
}
3488

35-
return fd;
89+
return leader_fd;
3690
}
3791

3892
int end(int fd, struct counter *out)
@@ -43,24 +97,50 @@ int end(int fd, struct counter *out)
4397
return -1;
4498
}
4599

100+
// Disable the event group
46101
if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1)
47102
{
48103
fprintf(stderr, "Error disabling perf event (fd: %d): %s\n", fd, strerror(errno));
49104
return -1;
50105
}
51106

52-
uint64_t read_format[5];
53-
if (read(fd, read_format, sizeof(read_format)) == -1)
107+
// Allocate buffer for reading results
108+
int size = sizeof(struct read_format) + (sizeof(uint64_t) * NUM_EVENTS);
109+
struct read_format *buffer = (struct read_format *)malloc(size);
110+
if (!buffer)
111+
{
112+
fprintf(stderr, "Failed to allocate memory for read buffer.\n");
113+
return -1;
114+
}
115+
116+
// Read results
117+
int ret_val = read(fd, buffer, size);
118+
if (ret_val == -1)
119+
{
120+
fprintf(stderr, "Error reading perf event results: %s\n", strerror(errno));
121+
free(buffer);
122+
return -1;
123+
}
124+
else if (ret_val != size)
54125
{
55-
fprintf(stderr, "Error reading perf event results (fd: %d): %s\n", fd, strerror(errno));
126+
fprintf(stderr, "Error reading perf event results: read %d bytes, expected %d\n", ret_val, size);
127+
free(buffer);
56128
return -1;
57129
}
58-
else
130+
131+
// Assign time_enabled_ns
132+
out->time_enabled_ns = buffer->time_enabled;
133+
134+
// Directly assign values to struct fields treating them as an array 8)
135+
uint64_t *counter_ptr = (uint64_t *)out;
136+
counter_ptr++; // Now points to instruction_count, the first counter field
137+
138+
for (int i = 0; i < NUM_EVENTS; i++)
59139
{
60-
close(fd);
61-
out->instruction_count = read_format[0];
62-
out->time_enabled_ns = read_format[1];
140+
counter_ptr[i] = buffer->values[i].value;
63141
}
64142

143+
close(fd);
144+
free(buffer);
65145
return 0;
66146
}

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name="Cirron",
5-
version="0.1",
5+
version="0.2",
66
packages=["cirron"],
77
package_data={
88
"cirron": ["cirronlib.c"],

0 commit comments

Comments
 (0)